@@ -6,13 +6,13 @@ |
||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | if (!defined("GROMMUNIOSYNC_VERSION")) { |
| 9 | - $path = escapeshellarg(dirname(realpath($_SERVER['SCRIPT_FILENAME']))); |
|
| 10 | - $branch = trim(exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\"")); |
|
| 11 | - $version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe --always 2>/dev/null"); |
|
| 12 | - if ($branch && $version) { |
|
| 13 | - define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version); |
|
| 14 | - } |
|
| 15 | - else { |
|
| 16 | - define("GROMMUNIOSYNC_VERSION", "GIT"); |
|
| 17 | - } |
|
| 9 | + $path = escapeshellarg(dirname(realpath($_SERVER['SCRIPT_FILENAME']))); |
|
| 10 | + $branch = trim(exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\"")); |
|
| 11 | + $version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe --always 2>/dev/null"); |
|
| 12 | + if ($branch && $version) { |
|
| 13 | + define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version); |
|
| 14 | + } |
|
| 15 | + else { |
|
| 16 | + define("GROMMUNIOSYNC_VERSION", "GIT"); |
|
| 17 | + } |
|
| 18 | 18 | } |
@@ -10,7 +10,7 @@ |
||
| 10 | 10 | $branch = trim(exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\"")); |
| 11 | 11 | $version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe --always 2>/dev/null"); |
| 12 | 12 | if ($branch && $version) { |
| 13 | - define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version); |
|
| 13 | + define("GROMMUNIOSYNC_VERSION", $branch.'-'.$version); |
|
| 14 | 14 | } |
| 15 | 15 | else { |
| 16 | 16 | define("GROMMUNIOSYNC_VERSION", "GIT"); |
@@ -11,8 +11,7 @@ |
||
| 11 | 11 | $version = exec("hash git 2>/dev/null && cd {$path} >/dev/null 2>&1 && git describe --always 2>/dev/null"); |
| 12 | 12 | if ($branch && $version) { |
| 13 | 13 | define("GROMMUNIOSYNC_VERSION", $branch . '-' . $version); |
| 14 | - } |
|
| 15 | - else { |
|
| 14 | + } else { |
|
| 16 | 15 | define("GROMMUNIOSYNC_VERSION", "GIT"); |
| 17 | 16 | } |
| 18 | 17 | } |
@@ -6,7 +6,7 @@ discard block |
||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | class Meetingrequest { |
| 9 | - /* |
|
| 9 | + /* |
|
| 10 | 10 | * NOTE |
| 11 | 11 | * |
| 12 | 12 | * This class is designed to modify and update meeting request properties |
@@ -22,7 +22,7 @@ discard block |
||
| 22 | 22 | * |
| 23 | 23 | */ |
| 24 | 24 | |
| 25 | - /* |
|
| 25 | + /* |
|
| 26 | 26 | * How to use |
| 27 | 27 | * ---------- |
| 28 | 28 | * |
@@ -73,666 +73,666 @@ discard block |
||
| 73 | 73 | * meeting object from calendar |
| 74 | 74 | */ |
| 75 | 75 | |
| 76 | - // All properties for a recipient that are interesting |
|
| 77 | - public $recipprops = [ |
|
| 78 | - PR_ENTRYID, |
|
| 79 | - PR_DISPLAY_NAME, |
|
| 80 | - PR_EMAIL_ADDRESS, |
|
| 81 | - PR_RECIPIENT_ENTRYID, |
|
| 82 | - PR_RECIPIENT_TYPE, |
|
| 83 | - PR_SEND_INTERNET_ENCODING, |
|
| 84 | - PR_SEND_RICH_INFO, |
|
| 85 | - PR_RECIPIENT_DISPLAY_NAME, |
|
| 86 | - PR_ADDRTYPE, |
|
| 87 | - PR_DISPLAY_TYPE, |
|
| 88 | - PR_DISPLAY_TYPE_EX, |
|
| 89 | - PR_RECIPIENT_TRACKSTATUS, |
|
| 90 | - PR_RECIPIENT_TRACKSTATUS_TIME, |
|
| 91 | - PR_RECIPIENT_FLAGS, |
|
| 92 | - PR_ROWID, |
|
| 93 | - PR_OBJECT_TYPE, |
|
| 94 | - PR_SEARCH_KEY, |
|
| 95 | - ]; |
|
| 96 | - |
|
| 97 | - /** |
|
| 98 | - * Indication whether the setting of resources in a Meeting Request is success (false) or if it |
|
| 99 | - * has failed (integer). |
|
| 100 | - */ |
|
| 101 | - public $errorSetResource; |
|
| 102 | - |
|
| 103 | - private $proptags; |
|
| 104 | - private $store; |
|
| 105 | - private $message; |
|
| 106 | - private $session; |
|
| 107 | - private $meetingTimeInfo; |
|
| 108 | - private $enableDirectBooking; |
|
| 109 | - private $includesResources; |
|
| 110 | - private $nonAcceptingResources; |
|
| 111 | - private $recipientDisplayname; |
|
| 112 | - |
|
| 113 | - /** |
|
| 114 | - * Constructor. |
|
| 115 | - * |
|
| 116 | - * Takes a store and a message. The message is an appointment item |
|
| 117 | - * that should be converted into a meeting request or an incoming |
|
| 118 | - * e-mail message that is a meeting request. |
|
| 119 | - * |
|
| 120 | - * The $session variable is optional, but required if the following features |
|
| 121 | - * are to be used: |
|
| 122 | - * |
|
| 123 | - * - Sending meeting requests for meetings that are not in your own store |
|
| 124 | - * - Sending meeting requests to resources, resource availability checking and resource freebusy updates |
|
| 125 | - * |
|
| 126 | - * @param mixed $store |
|
| 127 | - * @param mixed $message |
|
| 128 | - * @param mixed $session |
|
| 129 | - * @param mixed $enableDirectBooking |
|
| 130 | - */ |
|
| 131 | - public function __construct($store, $message, $session = false, $enableDirectBooking = true) { |
|
| 132 | - $this->store = $store; |
|
| 133 | - $this->message = $message; |
|
| 134 | - $this->session = $session; |
|
| 135 | - // This variable string saves time information for the MR. |
|
| 136 | - $this->meetingTimeInfo = false; |
|
| 137 | - $this->enableDirectBooking = $enableDirectBooking; |
|
| 138 | - |
|
| 139 | - $properties['goid'] = 'PT_BINARY:PSETID_Meeting:0x3'; |
|
| 140 | - $properties['goid2'] = 'PT_BINARY:PSETID_Meeting:0x23'; |
|
| 141 | - $properties['type'] = 'PT_STRING8:PSETID_Meeting:0x24'; |
|
| 142 | - $properties['meetingrecurring'] = 'PT_BOOLEAN:PSETID_Meeting:0x5'; |
|
| 143 | - $properties['unknown2'] = 'PT_BOOLEAN:PSETID_Meeting:0xa'; |
|
| 144 | - $properties['attendee_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1'; |
|
| 145 | - $properties['owner_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1a'; |
|
| 146 | - $properties['meetingstatus'] = 'PT_LONG:PSETID_Appointment:0x8217'; |
|
| 147 | - $properties['responsestatus'] = 'PT_LONG:PSETID_Appointment:0x8218'; |
|
| 148 | - $properties['unknown6'] = 'PT_LONG:PSETID_Meeting:0x4'; |
|
| 149 | - $properties['replytime'] = 'PT_SYSTIME:PSETID_Appointment:0x8220'; |
|
| 150 | - $properties['usetnef'] = 'PT_BOOLEAN:PSETID_Common:0x8582'; |
|
| 151 | - $properties['recurrence_data'] = 'PT_BINARY:PSETID_Appointment:0x8216'; |
|
| 152 | - $properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501'; |
|
| 153 | - $properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503'; |
|
| 154 | - $properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200'; |
|
| 155 | - $properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201'; // AppointmentSequenceNumber |
|
| 156 | - $properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203'; // AppointmentLastSequence |
|
| 157 | - $properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202'; |
|
| 158 | - $properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205'; |
|
| 159 | - $properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224'; |
|
| 160 | - $properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d'; |
|
| 161 | - $properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2'; |
|
| 162 | - $properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208'; |
|
| 163 | - $properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229'; // PidLidFInvited, MeetingRequestWasSent |
|
| 164 | - $properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d'; |
|
| 165 | - $properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e'; |
|
| 166 | - $properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560'; |
|
| 167 | - $properties['commonstart'] = 'PT_SYSTIME:PSETID_Common:0x8516'; |
|
| 168 | - $properties['commonend'] = 'PT_SYSTIME:PSETID_Common:0x8517'; |
|
| 169 | - $properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223'; |
|
| 170 | - $properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235'; |
|
| 171 | - $properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236'; |
|
| 172 | - $properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD'; // StartRecurTime |
|
| 173 | - $properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE'; // StartRecurTime |
|
| 174 | - $properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF'; // EndRecurDate |
|
| 175 | - $properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10'; // EndRecurTime |
|
| 176 | - $properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA'; // LID_IS_EXCEPTION |
|
| 177 | - $properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230'; |
|
| 178 | - // Propose new time properties |
|
| 179 | - $properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250'; |
|
| 180 | - $properties['proposed_end_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8251'; |
|
| 181 | - $properties['proposed_duration'] = 'PT_LONG:PSETID_Appointment:0x8256'; |
|
| 182 | - $properties['counter_proposal'] = 'PT_BOOLEAN:PSETID_Appointment:0x8257'; |
|
| 183 | - $properties['recurring_pattern'] = 'PT_STRING8:PSETID_Appointment:0x8232'; |
|
| 184 | - $properties['basedate'] = 'PT_SYSTIME:PSETID_Appointment:0x8228'; |
|
| 185 | - $properties['meetingtype'] = 'PT_LONG:PSETID_Meeting:0x26'; |
|
| 186 | - $properties['timezone_data'] = 'PT_BINARY:PSETID_Appointment:0x8233'; |
|
| 187 | - $properties['timezone'] = 'PT_STRING8:PSETID_Appointment:0x8234'; |
|
| 188 | - $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 189 | - $properties['toattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823B'; |
|
| 190 | - $properties['ccattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823C'; |
|
| 191 | - |
|
| 192 | - $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 193 | - } |
|
| 194 | - |
|
| 195 | - /** |
|
| 196 | - * Sets the direct booking property. This is an alternative to the setting of the direct booking |
|
| 197 | - * property through the constructor. However, setting it in the constructor is preferred. |
|
| 198 | - * |
|
| 199 | - * @param bool $directBookingSetting |
|
| 200 | - */ |
|
| 201 | - public function setDirectBooking($directBookingSetting) { |
|
| 202 | - $this->enableDirectBooking = $directBookingSetting; |
|
| 203 | - } |
|
| 204 | - |
|
| 205 | - /** |
|
| 206 | - * Returns TRUE if the message pointed to is an incoming meeting request and should |
|
| 207 | - * therefore be replied to with doAccept or doDecline(). |
|
| 208 | - * |
|
| 209 | - * @param string $messageClass message class to use for checking |
|
| 210 | - * |
|
| 211 | - * @return bool returns true if this is a meeting request else false |
|
| 212 | - */ |
|
| 213 | - public function isMeetingRequest($messageClass = false) { |
|
| 214 | - if ($messageClass === false) { |
|
| 215 | - $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]); |
|
| 216 | - $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false; |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.request') === 0) { |
|
| 220 | - return true; |
|
| 221 | - } |
|
| 222 | - |
|
| 223 | - return false; |
|
| 224 | - } |
|
| 225 | - |
|
| 226 | - /** |
|
| 227 | - * Returns TRUE if the message pointed to is a returning meeting request response. |
|
| 228 | - * |
|
| 229 | - * @param string $messageClass message class to use for checking |
|
| 230 | - * |
|
| 231 | - * @return bool returns true if this is a meeting request else false |
|
| 232 | - */ |
|
| 233 | - public function isMeetingRequestResponse($messageClass = false) { |
|
| 234 | - if ($messageClass === false) { |
|
| 235 | - $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]); |
|
| 236 | - $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false; |
|
| 237 | - } |
|
| 238 | - |
|
| 239 | - if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.resp') === 0) { |
|
| 240 | - return true; |
|
| 241 | - } |
|
| 242 | - |
|
| 243 | - return false; |
|
| 244 | - } |
|
| 245 | - |
|
| 246 | - /** |
|
| 247 | - * Returns TRUE if the message pointed to is a cancellation request. |
|
| 248 | - * |
|
| 249 | - * @param string $messageClass message class to use for checking |
|
| 250 | - * |
|
| 251 | - * @return bool returns true if this is a meeting request else false |
|
| 252 | - */ |
|
| 253 | - public function isMeetingCancellation($messageClass = false) { |
|
| 254 | - if ($messageClass === false) { |
|
| 255 | - $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]); |
|
| 256 | - $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false; |
|
| 257 | - } |
|
| 258 | - |
|
| 259 | - if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.canceled') === 0) { |
|
| 260 | - return true; |
|
| 261 | - } |
|
| 262 | - |
|
| 263 | - return false; |
|
| 264 | - } |
|
| 265 | - |
|
| 266 | - /** |
|
| 267 | - * Function is used to get the last update counter of meeting request. |
|
| 268 | - * |
|
| 269 | - * @return bool|Number false when last_updatecounter not found else return last_updatecounter |
|
| 270 | - */ |
|
| 271 | - public function getLastUpdateCounter() { |
|
| 272 | - $calendarItemProps = mapi_getprops($this->message, [$this->proptags['last_updatecounter']]); |
|
| 273 | - if (isset($calendarItemProps) && !empty($calendarItemProps)) { |
|
| 274 | - return $calendarItemProps[$this->proptags['last_updatecounter']]; |
|
| 275 | - } |
|
| 276 | - |
|
| 277 | - return false; |
|
| 278 | - } |
|
| 279 | - |
|
| 280 | - /** |
|
| 281 | - * Process an incoming meeting request response. This updates the appointment |
|
| 282 | - * in your calendar to show whether the user has accepted or declined. |
|
| 283 | - */ |
|
| 284 | - public function processMeetingRequestResponse() { |
|
| 285 | - if (!$this->isMeetingRequestResponse()) { |
|
| 286 | - return; |
|
| 287 | - } |
|
| 288 | - |
|
| 289 | - if (!$this->isLocalOrganiser()) { |
|
| 290 | - return; |
|
| 291 | - } |
|
| 292 | - |
|
| 293 | - // Get information we need from the response message |
|
| 294 | - $messageprops = mapi_getprops($this->message, [ |
|
| 295 | - $this->proptags['goid'], |
|
| 296 | - $this->proptags['goid2'], |
|
| 297 | - PR_OWNER_APPT_ID, |
|
| 298 | - PR_SENT_REPRESENTING_EMAIL_ADDRESS, |
|
| 299 | - PR_SENT_REPRESENTING_NAME, |
|
| 300 | - PR_SENT_REPRESENTING_ADDRTYPE, |
|
| 301 | - PR_SENT_REPRESENTING_ENTRYID, |
|
| 302 | - PR_SENT_REPRESENTING_SEARCH_KEY, |
|
| 303 | - PR_MESSAGE_DELIVERY_TIME, |
|
| 304 | - PR_MESSAGE_CLASS, |
|
| 305 | - PR_PROCESSED, |
|
| 306 | - PR_RCVD_REPRESENTING_ENTRYID, |
|
| 307 | - $this->proptags['proposed_start_whole'], |
|
| 308 | - $this->proptags['proposed_end_whole'], |
|
| 309 | - $this->proptags['proposed_duration'], |
|
| 310 | - $this->proptags['counter_proposal'], |
|
| 311 | - $this->proptags['attendee_critical_change'], |
|
| 312 | - ]); |
|
| 313 | - |
|
| 314 | - $goid2 = $messageprops[$this->proptags['goid2']]; |
|
| 315 | - |
|
| 316 | - if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) { |
|
| 317 | - return; |
|
| 318 | - } |
|
| 319 | - |
|
| 320 | - // Find basedate in GlobalID(0x3), this can be a response for an occurrence |
|
| 321 | - $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]); |
|
| 322 | - |
|
| 323 | - // check if delegate is processing the response |
|
| 324 | - if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 325 | - $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 326 | - |
|
| 327 | - $userStore = $delegatorStore['store']; |
|
| 328 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 329 | - } |
|
| 330 | - else { |
|
| 331 | - $userStore = $this->store; |
|
| 332 | - $calFolder = $this->openDefaultCalendar(); |
|
| 333 | - } |
|
| 334 | - |
|
| 335 | - // check for calendar access |
|
| 336 | - if ($this->checkCalendarWriteAccess($userStore) !== true) { |
|
| 337 | - // Throw an exception that we don't have write permissions on calendar folder, |
|
| 338 | - // allow caller to fill the error message |
|
| 339 | - throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 340 | - } |
|
| 341 | - |
|
| 342 | - $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 343 | - |
|
| 344 | - // Open the calendar items, and update all the recipients of the calendar item that match |
|
| 345 | - // the email address of the response. |
|
| 346 | - if ($calendarItem !== false) { |
|
| 347 | - $this->processResponse($userStore, $calendarItem, $basedate, $messageprops); |
|
| 348 | - } |
|
| 349 | - } |
|
| 350 | - |
|
| 351 | - /** |
|
| 352 | - * Process every incoming MeetingRequest response.This updates the appointment |
|
| 353 | - * in your calendar to show whether the user has accepted or declined. |
|
| 354 | - * |
|
| 355 | - * @param resource $store contains the userStore in which the meeting is created |
|
| 356 | - * @param MAPIMessage $calendarItem resource of the calendar item for which this response has arrived |
|
| 357 | - * @param bool $basedate if present the create an exception |
|
| 358 | - * @param array $messageprops contains message properties |
|
| 359 | - */ |
|
| 360 | - public function processResponse($store, $calendarItem, $basedate, $messageprops) { |
|
| 361 | - $senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 362 | - $messageclass = $messageprops[PR_MESSAGE_CLASS]; |
|
| 363 | - $deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME]; |
|
| 364 | - |
|
| 365 | - // Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match |
|
| 366 | - // the email address of the response. |
|
| 367 | - $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]); |
|
| 368 | - |
|
| 369 | - // check if meeting response is already processed |
|
| 370 | - if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
|
| 371 | - // meeting is already processed |
|
| 372 | - return; |
|
| 373 | - } |
|
| 374 | - mapi_setprops($this->message, [PR_PROCESSED => true]); |
|
| 375 | - mapi_savechanges($this->message); |
|
| 376 | - |
|
| 377 | - // if meeting is updated in organizer's calendar then we don't need to process |
|
| 378 | - // old response |
|
| 379 | - if ($this->isMeetingUpdated($basedate)) { |
|
| 380 | - return; |
|
| 381 | - } |
|
| 382 | - |
|
| 383 | - // If basedate is found, then create/modify exception msg and do processing |
|
| 384 | - if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) { |
|
| 385 | - $recurr = new Recurrence($store, $calendarItem); |
|
| 386 | - |
|
| 387 | - // Copy properties from meeting request |
|
| 388 | - $exception_props = mapi_getprops($this->message, [ |
|
| 389 | - PR_OWNER_APPT_ID, |
|
| 390 | - $this->proptags['proposed_start_whole'], |
|
| 391 | - $this->proptags['proposed_end_whole'], |
|
| 392 | - $this->proptags['proposed_duration'], |
|
| 393 | - $this->proptags['counter_proposal'], |
|
| 394 | - ]); |
|
| 395 | - |
|
| 396 | - // Create/modify exception |
|
| 397 | - if ($recurr->isException($basedate)) { |
|
| 398 | - $recurr->modifyException($exception_props, $basedate); |
|
| 399 | - } |
|
| 400 | - else { |
|
| 401 | - // When we are creating an exception we need copy recipients from main recurring item |
|
| 402 | - $recipTable = mapi_message_getrecipienttable($calendarItem); |
|
| 403 | - $recips = mapi_table_queryallrows($recipTable, $this->recipprops); |
|
| 404 | - |
|
| 405 | - // Retrieve actual start/due dates from calendar item. |
|
| 406 | - $exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
|
| 407 | - $exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
|
| 408 | - |
|
| 409 | - $recurr->createException($exception_props, $basedate, false, $recips); |
|
| 410 | - } |
|
| 411 | - |
|
| 412 | - mapi_savechanges($calendarItem); |
|
| 413 | - |
|
| 414 | - $attach = $recurr->getExceptionAttachment($basedate); |
|
| 415 | - if ($attach) { |
|
| 416 | - $recurringItem = $calendarItem; |
|
| 417 | - $calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 418 | - } |
|
| 419 | - else { |
|
| 420 | - return false; |
|
| 421 | - } |
|
| 422 | - } |
|
| 423 | - |
|
| 424 | - // Get the recipients of the calendar item |
|
| 425 | - $reciptable = mapi_message_getrecipienttable($calendarItem); |
|
| 426 | - $recipients = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 427 | - |
|
| 428 | - // FIXME we should look at the updatecounter property and compare it |
|
| 429 | - // to the counter in the recipient to see if this update is actually |
|
| 430 | - // newer than the status in the calendar item |
|
| 431 | - $found = false; |
|
| 432 | - |
|
| 433 | - $totalrecips = 0; |
|
| 434 | - $acceptedrecips = 0; |
|
| 435 | - foreach ($recipients as $recipient) { |
|
| 436 | - ++$totalrecips; |
|
| 437 | - if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) { |
|
| 438 | - $found = true; |
|
| 439 | - |
|
| 440 | - /* |
|
| 76 | + // All properties for a recipient that are interesting |
|
| 77 | + public $recipprops = [ |
|
| 78 | + PR_ENTRYID, |
|
| 79 | + PR_DISPLAY_NAME, |
|
| 80 | + PR_EMAIL_ADDRESS, |
|
| 81 | + PR_RECIPIENT_ENTRYID, |
|
| 82 | + PR_RECIPIENT_TYPE, |
|
| 83 | + PR_SEND_INTERNET_ENCODING, |
|
| 84 | + PR_SEND_RICH_INFO, |
|
| 85 | + PR_RECIPIENT_DISPLAY_NAME, |
|
| 86 | + PR_ADDRTYPE, |
|
| 87 | + PR_DISPLAY_TYPE, |
|
| 88 | + PR_DISPLAY_TYPE_EX, |
|
| 89 | + PR_RECIPIENT_TRACKSTATUS, |
|
| 90 | + PR_RECIPIENT_TRACKSTATUS_TIME, |
|
| 91 | + PR_RECIPIENT_FLAGS, |
|
| 92 | + PR_ROWID, |
|
| 93 | + PR_OBJECT_TYPE, |
|
| 94 | + PR_SEARCH_KEY, |
|
| 95 | + ]; |
|
| 96 | + |
|
| 97 | + /** |
|
| 98 | + * Indication whether the setting of resources in a Meeting Request is success (false) or if it |
|
| 99 | + * has failed (integer). |
|
| 100 | + */ |
|
| 101 | + public $errorSetResource; |
|
| 102 | + |
|
| 103 | + private $proptags; |
|
| 104 | + private $store; |
|
| 105 | + private $message; |
|
| 106 | + private $session; |
|
| 107 | + private $meetingTimeInfo; |
|
| 108 | + private $enableDirectBooking; |
|
| 109 | + private $includesResources; |
|
| 110 | + private $nonAcceptingResources; |
|
| 111 | + private $recipientDisplayname; |
|
| 112 | + |
|
| 113 | + /** |
|
| 114 | + * Constructor. |
|
| 115 | + * |
|
| 116 | + * Takes a store and a message. The message is an appointment item |
|
| 117 | + * that should be converted into a meeting request or an incoming |
|
| 118 | + * e-mail message that is a meeting request. |
|
| 119 | + * |
|
| 120 | + * The $session variable is optional, but required if the following features |
|
| 121 | + * are to be used: |
|
| 122 | + * |
|
| 123 | + * - Sending meeting requests for meetings that are not in your own store |
|
| 124 | + * - Sending meeting requests to resources, resource availability checking and resource freebusy updates |
|
| 125 | + * |
|
| 126 | + * @param mixed $store |
|
| 127 | + * @param mixed $message |
|
| 128 | + * @param mixed $session |
|
| 129 | + * @param mixed $enableDirectBooking |
|
| 130 | + */ |
|
| 131 | + public function __construct($store, $message, $session = false, $enableDirectBooking = true) { |
|
| 132 | + $this->store = $store; |
|
| 133 | + $this->message = $message; |
|
| 134 | + $this->session = $session; |
|
| 135 | + // This variable string saves time information for the MR. |
|
| 136 | + $this->meetingTimeInfo = false; |
|
| 137 | + $this->enableDirectBooking = $enableDirectBooking; |
|
| 138 | + |
|
| 139 | + $properties['goid'] = 'PT_BINARY:PSETID_Meeting:0x3'; |
|
| 140 | + $properties['goid2'] = 'PT_BINARY:PSETID_Meeting:0x23'; |
|
| 141 | + $properties['type'] = 'PT_STRING8:PSETID_Meeting:0x24'; |
|
| 142 | + $properties['meetingrecurring'] = 'PT_BOOLEAN:PSETID_Meeting:0x5'; |
|
| 143 | + $properties['unknown2'] = 'PT_BOOLEAN:PSETID_Meeting:0xa'; |
|
| 144 | + $properties['attendee_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1'; |
|
| 145 | + $properties['owner_critical_change'] = 'PT_SYSTIME:PSETID_Meeting:0x1a'; |
|
| 146 | + $properties['meetingstatus'] = 'PT_LONG:PSETID_Appointment:0x8217'; |
|
| 147 | + $properties['responsestatus'] = 'PT_LONG:PSETID_Appointment:0x8218'; |
|
| 148 | + $properties['unknown6'] = 'PT_LONG:PSETID_Meeting:0x4'; |
|
| 149 | + $properties['replytime'] = 'PT_SYSTIME:PSETID_Appointment:0x8220'; |
|
| 150 | + $properties['usetnef'] = 'PT_BOOLEAN:PSETID_Common:0x8582'; |
|
| 151 | + $properties['recurrence_data'] = 'PT_BINARY:PSETID_Appointment:0x8216'; |
|
| 152 | + $properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501'; |
|
| 153 | + $properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503'; |
|
| 154 | + $properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200'; |
|
| 155 | + $properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201'; // AppointmentSequenceNumber |
|
| 156 | + $properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203'; // AppointmentLastSequence |
|
| 157 | + $properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202'; |
|
| 158 | + $properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205'; |
|
| 159 | + $properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224'; |
|
| 160 | + $properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d'; |
|
| 161 | + $properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2'; |
|
| 162 | + $properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208'; |
|
| 163 | + $properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229'; // PidLidFInvited, MeetingRequestWasSent |
|
| 164 | + $properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d'; |
|
| 165 | + $properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e'; |
|
| 166 | + $properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560'; |
|
| 167 | + $properties['commonstart'] = 'PT_SYSTIME:PSETID_Common:0x8516'; |
|
| 168 | + $properties['commonend'] = 'PT_SYSTIME:PSETID_Common:0x8517'; |
|
| 169 | + $properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223'; |
|
| 170 | + $properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235'; |
|
| 171 | + $properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236'; |
|
| 172 | + $properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD'; // StartRecurTime |
|
| 173 | + $properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE'; // StartRecurTime |
|
| 174 | + $properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF'; // EndRecurDate |
|
| 175 | + $properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10'; // EndRecurTime |
|
| 176 | + $properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA'; // LID_IS_EXCEPTION |
|
| 177 | + $properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230'; |
|
| 178 | + // Propose new time properties |
|
| 179 | + $properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250'; |
|
| 180 | + $properties['proposed_end_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8251'; |
|
| 181 | + $properties['proposed_duration'] = 'PT_LONG:PSETID_Appointment:0x8256'; |
|
| 182 | + $properties['counter_proposal'] = 'PT_BOOLEAN:PSETID_Appointment:0x8257'; |
|
| 183 | + $properties['recurring_pattern'] = 'PT_STRING8:PSETID_Appointment:0x8232'; |
|
| 184 | + $properties['basedate'] = 'PT_SYSTIME:PSETID_Appointment:0x8228'; |
|
| 185 | + $properties['meetingtype'] = 'PT_LONG:PSETID_Meeting:0x26'; |
|
| 186 | + $properties['timezone_data'] = 'PT_BINARY:PSETID_Appointment:0x8233'; |
|
| 187 | + $properties['timezone'] = 'PT_STRING8:PSETID_Appointment:0x8234'; |
|
| 188 | + $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 189 | + $properties['toattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823B'; |
|
| 190 | + $properties['ccattendeesstring'] = 'PT_STRING8:PSETID_Appointment:0x823C'; |
|
| 191 | + |
|
| 192 | + $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 193 | + } |
|
| 194 | + |
|
| 195 | + /** |
|
| 196 | + * Sets the direct booking property. This is an alternative to the setting of the direct booking |
|
| 197 | + * property through the constructor. However, setting it in the constructor is preferred. |
|
| 198 | + * |
|
| 199 | + * @param bool $directBookingSetting |
|
| 200 | + */ |
|
| 201 | + public function setDirectBooking($directBookingSetting) { |
|
| 202 | + $this->enableDirectBooking = $directBookingSetting; |
|
| 203 | + } |
|
| 204 | + |
|
| 205 | + /** |
|
| 206 | + * Returns TRUE if the message pointed to is an incoming meeting request and should |
|
| 207 | + * therefore be replied to with doAccept or doDecline(). |
|
| 208 | + * |
|
| 209 | + * @param string $messageClass message class to use for checking |
|
| 210 | + * |
|
| 211 | + * @return bool returns true if this is a meeting request else false |
|
| 212 | + */ |
|
| 213 | + public function isMeetingRequest($messageClass = false) { |
|
| 214 | + if ($messageClass === false) { |
|
| 215 | + $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]); |
|
| 216 | + $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false; |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.request') === 0) { |
|
| 220 | + return true; |
|
| 221 | + } |
|
| 222 | + |
|
| 223 | + return false; |
|
| 224 | + } |
|
| 225 | + |
|
| 226 | + /** |
|
| 227 | + * Returns TRUE if the message pointed to is a returning meeting request response. |
|
| 228 | + * |
|
| 229 | + * @param string $messageClass message class to use for checking |
|
| 230 | + * |
|
| 231 | + * @return bool returns true if this is a meeting request else false |
|
| 232 | + */ |
|
| 233 | + public function isMeetingRequestResponse($messageClass = false) { |
|
| 234 | + if ($messageClass === false) { |
|
| 235 | + $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]); |
|
| 236 | + $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false; |
|
| 237 | + } |
|
| 238 | + |
|
| 239 | + if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.resp') === 0) { |
|
| 240 | + return true; |
|
| 241 | + } |
|
| 242 | + |
|
| 243 | + return false; |
|
| 244 | + } |
|
| 245 | + |
|
| 246 | + /** |
|
| 247 | + * Returns TRUE if the message pointed to is a cancellation request. |
|
| 248 | + * |
|
| 249 | + * @param string $messageClass message class to use for checking |
|
| 250 | + * |
|
| 251 | + * @return bool returns true if this is a meeting request else false |
|
| 252 | + */ |
|
| 253 | + public function isMeetingCancellation($messageClass = false) { |
|
| 254 | + if ($messageClass === false) { |
|
| 255 | + $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]); |
|
| 256 | + $messageClass = isset($props[PR_MESSAGE_CLASS]) ? $props[PR_MESSAGE_CLASS] : false; |
|
| 257 | + } |
|
| 258 | + |
|
| 259 | + if ($messageClass !== false && stripos($messageClass, 'ipm.schedule.meeting.canceled') === 0) { |
|
| 260 | + return true; |
|
| 261 | + } |
|
| 262 | + |
|
| 263 | + return false; |
|
| 264 | + } |
|
| 265 | + |
|
| 266 | + /** |
|
| 267 | + * Function is used to get the last update counter of meeting request. |
|
| 268 | + * |
|
| 269 | + * @return bool|Number false when last_updatecounter not found else return last_updatecounter |
|
| 270 | + */ |
|
| 271 | + public function getLastUpdateCounter() { |
|
| 272 | + $calendarItemProps = mapi_getprops($this->message, [$this->proptags['last_updatecounter']]); |
|
| 273 | + if (isset($calendarItemProps) && !empty($calendarItemProps)) { |
|
| 274 | + return $calendarItemProps[$this->proptags['last_updatecounter']]; |
|
| 275 | + } |
|
| 276 | + |
|
| 277 | + return false; |
|
| 278 | + } |
|
| 279 | + |
|
| 280 | + /** |
|
| 281 | + * Process an incoming meeting request response. This updates the appointment |
|
| 282 | + * in your calendar to show whether the user has accepted or declined. |
|
| 283 | + */ |
|
| 284 | + public function processMeetingRequestResponse() { |
|
| 285 | + if (!$this->isMeetingRequestResponse()) { |
|
| 286 | + return; |
|
| 287 | + } |
|
| 288 | + |
|
| 289 | + if (!$this->isLocalOrganiser()) { |
|
| 290 | + return; |
|
| 291 | + } |
|
| 292 | + |
|
| 293 | + // Get information we need from the response message |
|
| 294 | + $messageprops = mapi_getprops($this->message, [ |
|
| 295 | + $this->proptags['goid'], |
|
| 296 | + $this->proptags['goid2'], |
|
| 297 | + PR_OWNER_APPT_ID, |
|
| 298 | + PR_SENT_REPRESENTING_EMAIL_ADDRESS, |
|
| 299 | + PR_SENT_REPRESENTING_NAME, |
|
| 300 | + PR_SENT_REPRESENTING_ADDRTYPE, |
|
| 301 | + PR_SENT_REPRESENTING_ENTRYID, |
|
| 302 | + PR_SENT_REPRESENTING_SEARCH_KEY, |
|
| 303 | + PR_MESSAGE_DELIVERY_TIME, |
|
| 304 | + PR_MESSAGE_CLASS, |
|
| 305 | + PR_PROCESSED, |
|
| 306 | + PR_RCVD_REPRESENTING_ENTRYID, |
|
| 307 | + $this->proptags['proposed_start_whole'], |
|
| 308 | + $this->proptags['proposed_end_whole'], |
|
| 309 | + $this->proptags['proposed_duration'], |
|
| 310 | + $this->proptags['counter_proposal'], |
|
| 311 | + $this->proptags['attendee_critical_change'], |
|
| 312 | + ]); |
|
| 313 | + |
|
| 314 | + $goid2 = $messageprops[$this->proptags['goid2']]; |
|
| 315 | + |
|
| 316 | + if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) { |
|
| 317 | + return; |
|
| 318 | + } |
|
| 319 | + |
|
| 320 | + // Find basedate in GlobalID(0x3), this can be a response for an occurrence |
|
| 321 | + $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]); |
|
| 322 | + |
|
| 323 | + // check if delegate is processing the response |
|
| 324 | + if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 325 | + $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 326 | + |
|
| 327 | + $userStore = $delegatorStore['store']; |
|
| 328 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 329 | + } |
|
| 330 | + else { |
|
| 331 | + $userStore = $this->store; |
|
| 332 | + $calFolder = $this->openDefaultCalendar(); |
|
| 333 | + } |
|
| 334 | + |
|
| 335 | + // check for calendar access |
|
| 336 | + if ($this->checkCalendarWriteAccess($userStore) !== true) { |
|
| 337 | + // Throw an exception that we don't have write permissions on calendar folder, |
|
| 338 | + // allow caller to fill the error message |
|
| 339 | + throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 340 | + } |
|
| 341 | + |
|
| 342 | + $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 343 | + |
|
| 344 | + // Open the calendar items, and update all the recipients of the calendar item that match |
|
| 345 | + // the email address of the response. |
|
| 346 | + if ($calendarItem !== false) { |
|
| 347 | + $this->processResponse($userStore, $calendarItem, $basedate, $messageprops); |
|
| 348 | + } |
|
| 349 | + } |
|
| 350 | + |
|
| 351 | + /** |
|
| 352 | + * Process every incoming MeetingRequest response.This updates the appointment |
|
| 353 | + * in your calendar to show whether the user has accepted or declined. |
|
| 354 | + * |
|
| 355 | + * @param resource $store contains the userStore in which the meeting is created |
|
| 356 | + * @param MAPIMessage $calendarItem resource of the calendar item for which this response has arrived |
|
| 357 | + * @param bool $basedate if present the create an exception |
|
| 358 | + * @param array $messageprops contains message properties |
|
| 359 | + */ |
|
| 360 | + public function processResponse($store, $calendarItem, $basedate, $messageprops) { |
|
| 361 | + $senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 362 | + $messageclass = $messageprops[PR_MESSAGE_CLASS]; |
|
| 363 | + $deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME]; |
|
| 364 | + |
|
| 365 | + // Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match |
|
| 366 | + // the email address of the response. |
|
| 367 | + $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]); |
|
| 368 | + |
|
| 369 | + // check if meeting response is already processed |
|
| 370 | + if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
|
| 371 | + // meeting is already processed |
|
| 372 | + return; |
|
| 373 | + } |
|
| 374 | + mapi_setprops($this->message, [PR_PROCESSED => true]); |
|
| 375 | + mapi_savechanges($this->message); |
|
| 376 | + |
|
| 377 | + // if meeting is updated in organizer's calendar then we don't need to process |
|
| 378 | + // old response |
|
| 379 | + if ($this->isMeetingUpdated($basedate)) { |
|
| 380 | + return; |
|
| 381 | + } |
|
| 382 | + |
|
| 383 | + // If basedate is found, then create/modify exception msg and do processing |
|
| 384 | + if ($basedate && isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] === true) { |
|
| 385 | + $recurr = new Recurrence($store, $calendarItem); |
|
| 386 | + |
|
| 387 | + // Copy properties from meeting request |
|
| 388 | + $exception_props = mapi_getprops($this->message, [ |
|
| 389 | + PR_OWNER_APPT_ID, |
|
| 390 | + $this->proptags['proposed_start_whole'], |
|
| 391 | + $this->proptags['proposed_end_whole'], |
|
| 392 | + $this->proptags['proposed_duration'], |
|
| 393 | + $this->proptags['counter_proposal'], |
|
| 394 | + ]); |
|
| 395 | + |
|
| 396 | + // Create/modify exception |
|
| 397 | + if ($recurr->isException($basedate)) { |
|
| 398 | + $recurr->modifyException($exception_props, $basedate); |
|
| 399 | + } |
|
| 400 | + else { |
|
| 401 | + // When we are creating an exception we need copy recipients from main recurring item |
|
| 402 | + $recipTable = mapi_message_getrecipienttable($calendarItem); |
|
| 403 | + $recips = mapi_table_queryallrows($recipTable, $this->recipprops); |
|
| 404 | + |
|
| 405 | + // Retrieve actual start/due dates from calendar item. |
|
| 406 | + $exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
|
| 407 | + $exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
|
| 408 | + |
|
| 409 | + $recurr->createException($exception_props, $basedate, false, $recips); |
|
| 410 | + } |
|
| 411 | + |
|
| 412 | + mapi_savechanges($calendarItem); |
|
| 413 | + |
|
| 414 | + $attach = $recurr->getExceptionAttachment($basedate); |
|
| 415 | + if ($attach) { |
|
| 416 | + $recurringItem = $calendarItem; |
|
| 417 | + $calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 418 | + } |
|
| 419 | + else { |
|
| 420 | + return false; |
|
| 421 | + } |
|
| 422 | + } |
|
| 423 | + |
|
| 424 | + // Get the recipients of the calendar item |
|
| 425 | + $reciptable = mapi_message_getrecipienttable($calendarItem); |
|
| 426 | + $recipients = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 427 | + |
|
| 428 | + // FIXME we should look at the updatecounter property and compare it |
|
| 429 | + // to the counter in the recipient to see if this update is actually |
|
| 430 | + // newer than the status in the calendar item |
|
| 431 | + $found = false; |
|
| 432 | + |
|
| 433 | + $totalrecips = 0; |
|
| 434 | + $acceptedrecips = 0; |
|
| 435 | + foreach ($recipients as $recipient) { |
|
| 436 | + ++$totalrecips; |
|
| 437 | + if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) { |
|
| 438 | + $found = true; |
|
| 439 | + |
|
| 440 | + /* |
|
| 441 | 441 | * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME |
| 442 | 442 | * on the corresponding recipientRow of meeting then we ignore this response mail. |
| 443 | 443 | */ |
| 444 | - if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) { |
|
| 445 | - continue; |
|
| 446 | - } |
|
| 447 | - |
|
| 448 | - // The email address matches, update the row |
|
| 449 | - $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass); |
|
| 450 | - $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']]; |
|
| 451 | - |
|
| 452 | - // If this is a counter proposal, set the proposal properties in the recipient row |
|
| 453 | - if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) { |
|
| 454 | - $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']]; |
|
| 455 | - $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']]; |
|
| 456 | - $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']]; |
|
| 457 | - } |
|
| 458 | - |
|
| 459 | - mapi_message_modifyrecipients($calendarItem, MODRECIP_MODIFY, [$recipient]); |
|
| 460 | - } |
|
| 461 | - if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) { |
|
| 462 | - ++$acceptedrecips; |
|
| 463 | - } |
|
| 464 | - } |
|
| 465 | - |
|
| 466 | - // If the recipient was not found in the original calendar item, |
|
| 467 | - // then add the recpient as a new optional recipient |
|
| 468 | - if (!$found) { |
|
| 469 | - $recipient = []; |
|
| 470 | - $recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 471 | - $recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 472 | - $recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME]; |
|
| 473 | - $recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 474 | - $recipient[PR_RECIPIENT_TYPE] = MAPI_CC; |
|
| 475 | - $recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 476 | - $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass); |
|
| 477 | - $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime; |
|
| 478 | - |
|
| 479 | - // If this is a counter proposal, set the proposal properties in the recipient row |
|
| 480 | - if (isset($messageprops[$this->proptags['counter_proposal']])) { |
|
| 481 | - $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']]; |
|
| 482 | - $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']]; |
|
| 483 | - $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']]; |
|
| 484 | - } |
|
| 485 | - |
|
| 486 | - mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]); |
|
| 487 | - ++$totalrecips; |
|
| 488 | - if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) { |
|
| 489 | - ++$acceptedrecips; |
|
| 490 | - } |
|
| 491 | - } |
|
| 492 | - |
|
| 493 | - // TODO: Update counter proposal number property on message |
|
| 494 | - /* |
|
| 444 | + if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) { |
|
| 445 | + continue; |
|
| 446 | + } |
|
| 447 | + |
|
| 448 | + // The email address matches, update the row |
|
| 449 | + $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass); |
|
| 450 | + $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']]; |
|
| 451 | + |
|
| 452 | + // If this is a counter proposal, set the proposal properties in the recipient row |
|
| 453 | + if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) { |
|
| 454 | + $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']]; |
|
| 455 | + $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']]; |
|
| 456 | + $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']]; |
|
| 457 | + } |
|
| 458 | + |
|
| 459 | + mapi_message_modifyrecipients($calendarItem, MODRECIP_MODIFY, [$recipient]); |
|
| 460 | + } |
|
| 461 | + if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) { |
|
| 462 | + ++$acceptedrecips; |
|
| 463 | + } |
|
| 464 | + } |
|
| 465 | + |
|
| 466 | + // If the recipient was not found in the original calendar item, |
|
| 467 | + // then add the recpient as a new optional recipient |
|
| 468 | + if (!$found) { |
|
| 469 | + $recipient = []; |
|
| 470 | + $recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 471 | + $recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 472 | + $recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME]; |
|
| 473 | + $recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 474 | + $recipient[PR_RECIPIENT_TYPE] = MAPI_CC; |
|
| 475 | + $recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 476 | + $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass); |
|
| 477 | + $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime; |
|
| 478 | + |
|
| 479 | + // If this is a counter proposal, set the proposal properties in the recipient row |
|
| 480 | + if (isset($messageprops[$this->proptags['counter_proposal']])) { |
|
| 481 | + $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']]; |
|
| 482 | + $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']]; |
|
| 483 | + $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']]; |
|
| 484 | + } |
|
| 485 | + |
|
| 486 | + mapi_message_modifyrecipients($calendarItem, MODRECIP_ADD, [$recipient]); |
|
| 487 | + ++$totalrecips; |
|
| 488 | + if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) { |
|
| 489 | + ++$acceptedrecips; |
|
| 490 | + } |
|
| 491 | + } |
|
| 492 | + |
|
| 493 | + // TODO: Update counter proposal number property on message |
|
| 494 | + /* |
|
| 495 | 495 | 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. |
| 496 | 496 | */ |
| 497 | - // If this is a counter proposal, set the counter proposal indicator boolean |
|
| 498 | - if (isset($messageprops[$this->proptags['counter_proposal']])) { |
|
| 499 | - $props = []; |
|
| 500 | - if ($messageprops[$this->proptags['counter_proposal']]) { |
|
| 501 | - $props[$this->proptags['counter_proposal']] = true; |
|
| 502 | - } |
|
| 503 | - else { |
|
| 504 | - $props[$this->proptags['counter_proposal']] = false; |
|
| 505 | - } |
|
| 506 | - |
|
| 507 | - mapi_setprops($calendarItem, $props); |
|
| 508 | - } |
|
| 509 | - |
|
| 510 | - mapi_savechanges($calendarItem); |
|
| 511 | - if (isset($attach)) { |
|
| 512 | - mapi_savechanges($attach); |
|
| 513 | - mapi_savechanges($recurringItem); |
|
| 514 | - } |
|
| 515 | - } |
|
| 516 | - |
|
| 517 | - /** |
|
| 518 | - * Process an incoming meeting request cancellation. This updates the |
|
| 519 | - * appointment in your calendar to show that the meeting has been cancelled. |
|
| 520 | - */ |
|
| 521 | - public function processMeetingCancellation() { |
|
| 522 | - if (!$this->isMeetingCancellation()) { |
|
| 523 | - return; |
|
| 524 | - } |
|
| 525 | - |
|
| 526 | - if ($this->isLocalOrganiser()) { |
|
| 527 | - return; |
|
| 528 | - } |
|
| 529 | - |
|
| 530 | - if (!$this->isInCalendar()) { |
|
| 531 | - return; |
|
| 532 | - } |
|
| 533 | - |
|
| 534 | - $listProperties = $this->proptags; |
|
| 535 | - $listProperties['subject'] = PR_SUBJECT; |
|
| 536 | - $listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME; |
|
| 537 | - $listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE; |
|
| 538 | - $listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS; |
|
| 539 | - $listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID; |
|
| 540 | - $listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY; |
|
| 541 | - $listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME; |
|
| 542 | - $listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE; |
|
| 543 | - $listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS; |
|
| 544 | - $listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID; |
|
| 545 | - $listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY; |
|
| 546 | - $messageProps = mapi_getprops($this->message, $listProperties); |
|
| 547 | - |
|
| 548 | - $goid = $messageProps[$this->proptags['goid']]; // GlobalID (0x3) |
|
| 549 | - if (!isset($goid)) { |
|
| 550 | - return; |
|
| 551 | - } |
|
| 552 | - |
|
| 553 | - // get delegator store, if delegate is processing this cancellation |
|
| 554 | - if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 555 | - $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 556 | - |
|
| 557 | - $store = $delegatorStore['store']; |
|
| 558 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 559 | - } |
|
| 560 | - else { |
|
| 561 | - $store = $this->store; |
|
| 562 | - $calFolder = $this->openDefaultCalendar(); |
|
| 563 | - } |
|
| 564 | - |
|
| 565 | - // check for calendar access |
|
| 566 | - if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 567 | - // Throw an exception that we don't have write permissions on calendar folder, |
|
| 568 | - // allow caller to fill the error message |
|
| 569 | - throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 570 | - } |
|
| 571 | - |
|
| 572 | - $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 573 | - $basedate = $this->getBasedateFromGlobalID($goid); |
|
| 574 | - |
|
| 575 | - if ($calendarItem !== false) { |
|
| 576 | - // if basedate is provided and we could not find the item then it could be that we are processing |
|
| 577 | - // an exception so get the exception and process it |
|
| 578 | - if ($basedate) { |
|
| 579 | - $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]); |
|
| 580 | - if ($calendarItemProps[$this->proptags['recurring']] === true) { |
|
| 581 | - $recurr = new Recurrence($store, $calendarItem); |
|
| 582 | - |
|
| 583 | - // Set message class |
|
| 584 | - $messageProps[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 585 | - |
|
| 586 | - if ($recurr->isException($basedate)) { |
|
| 587 | - $recurr->modifyException($messageProps, $basedate); |
|
| 588 | - } |
|
| 589 | - else { |
|
| 590 | - $recurr->createException($messageProps, $basedate); |
|
| 591 | - } |
|
| 592 | - } |
|
| 593 | - } |
|
| 594 | - else { |
|
| 595 | - // set the properties of the cancellation object |
|
| 596 | - mapi_setprops($calendarItem, $messageProps); |
|
| 597 | - } |
|
| 598 | - |
|
| 599 | - mapi_savechanges($calendarItem); |
|
| 600 | - } |
|
| 601 | - } |
|
| 602 | - |
|
| 603 | - /** |
|
| 604 | - * Returns true if the corresponding calendar items exists in the celendar folder for this |
|
| 605 | - * meeting request/response/cancellation. |
|
| 606 | - */ |
|
| 607 | - public function isInCalendar() { |
|
| 608 | - // @TODO check for deleted exceptions |
|
| 609 | - return $this->getCorrespondentCalendarItem(false) !== false; |
|
| 610 | - } |
|
| 611 | - |
|
| 612 | - /** |
|
| 613 | - * Accepts the meeting request by moving the item to the calendar |
|
| 614 | - * and sending a confirmation message back to the sender. If $tentative |
|
| 615 | - * is TRUE, then the item is accepted tentatively. After accepting, you |
|
| 616 | - * can't use this class instance any more. The message is closed. If you |
|
| 617 | - * specify TRUE for 'move', then the item is actually moved (from your |
|
| 618 | - * inbox probably) to the calendar. If you don't, it is copied into |
|
| 619 | - * your calendar. |
|
| 620 | - * |
|
| 621 | - * @param bool $tentative true if user as tentative accepted the meeting |
|
| 622 | - * @param bool $sendresponse true if a response has to be send to organizer |
|
| 623 | - * @param bool $move true if the meeting request should be moved to the deleted items after processing |
|
| 624 | - * @param string $newProposedStartTime contains starttime if user has proposed other time |
|
| 625 | - * @param string $newProposedEndTime contains endtime if user has proposed other time |
|
| 626 | - * @param string $basedate start of day of occurrence for which user has accepted the recurrent meeting |
|
| 627 | - * @param bool $isImported true to indicate that MR is imported from .ics or .vcs file else it false. |
|
| 628 | - * @param mixed $body |
|
| 629 | - * @param mixed $userAction |
|
| 630 | - * @param mixed $store |
|
| 631 | - * |
|
| 632 | - * @return string $entryid entryid of item which created/updated in calendar |
|
| 633 | - */ |
|
| 634 | - public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false, $isImported = false) { |
|
| 635 | - if ($this->isLocalOrganiser()) { |
|
| 636 | - return false; |
|
| 637 | - } |
|
| 638 | - |
|
| 639 | - // Remove any previous calendar items with this goid and appt id |
|
| 640 | - $messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['updatecounter'], PR_PROCESSED, PR_RCVD_REPRESENTING_ENTRYID, PR_SENDER_ENTRYID, PR_SENT_REPRESENTING_ENTRYID, PR_RECEIVED_BY_ENTRYID]); |
|
| 641 | - |
|
| 642 | - // If this meeting request is received by a delegate then open delegator's store. |
|
| 643 | - if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 644 | - $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 645 | - |
|
| 646 | - $store = $delegatorStore['store']; |
|
| 647 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 648 | - } |
|
| 649 | - else { |
|
| 650 | - $calFolder = $this->openDefaultCalendar(); |
|
| 651 | - $store = $this->store; |
|
| 652 | - } |
|
| 653 | - |
|
| 654 | - // check for calendar access |
|
| 655 | - if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 656 | - // Throw an exception that we don't have write permissions on calendar folder, |
|
| 657 | - // allow caller to fill the error message |
|
| 658 | - throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 659 | - } |
|
| 660 | - |
|
| 661 | - // if meeting is out dated then don't process it |
|
| 662 | - if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $this->isMeetingOutOfDate()) { |
|
| 663 | - return false; |
|
| 664 | - } |
|
| 665 | - |
|
| 666 | - /* |
|
| 497 | + // If this is a counter proposal, set the counter proposal indicator boolean |
|
| 498 | + if (isset($messageprops[$this->proptags['counter_proposal']])) { |
|
| 499 | + $props = []; |
|
| 500 | + if ($messageprops[$this->proptags['counter_proposal']]) { |
|
| 501 | + $props[$this->proptags['counter_proposal']] = true; |
|
| 502 | + } |
|
| 503 | + else { |
|
| 504 | + $props[$this->proptags['counter_proposal']] = false; |
|
| 505 | + } |
|
| 506 | + |
|
| 507 | + mapi_setprops($calendarItem, $props); |
|
| 508 | + } |
|
| 509 | + |
|
| 510 | + mapi_savechanges($calendarItem); |
|
| 511 | + if (isset($attach)) { |
|
| 512 | + mapi_savechanges($attach); |
|
| 513 | + mapi_savechanges($recurringItem); |
|
| 514 | + } |
|
| 515 | + } |
|
| 516 | + |
|
| 517 | + /** |
|
| 518 | + * Process an incoming meeting request cancellation. This updates the |
|
| 519 | + * appointment in your calendar to show that the meeting has been cancelled. |
|
| 520 | + */ |
|
| 521 | + public function processMeetingCancellation() { |
|
| 522 | + if (!$this->isMeetingCancellation()) { |
|
| 523 | + return; |
|
| 524 | + } |
|
| 525 | + |
|
| 526 | + if ($this->isLocalOrganiser()) { |
|
| 527 | + return; |
|
| 528 | + } |
|
| 529 | + |
|
| 530 | + if (!$this->isInCalendar()) { |
|
| 531 | + return; |
|
| 532 | + } |
|
| 533 | + |
|
| 534 | + $listProperties = $this->proptags; |
|
| 535 | + $listProperties['subject'] = PR_SUBJECT; |
|
| 536 | + $listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME; |
|
| 537 | + $listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE; |
|
| 538 | + $listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS; |
|
| 539 | + $listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID; |
|
| 540 | + $listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY; |
|
| 541 | + $listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME; |
|
| 542 | + $listProperties['rcvd_representing_address_type'] = PR_RCVD_REPRESENTING_ADDRTYPE; |
|
| 543 | + $listProperties['rcvd_representing_email_address'] = PR_RCVD_REPRESENTING_EMAIL_ADDRESS; |
|
| 544 | + $listProperties['rcvd_representing_entryid'] = PR_RCVD_REPRESENTING_ENTRYID; |
|
| 545 | + $listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY; |
|
| 546 | + $messageProps = mapi_getprops($this->message, $listProperties); |
|
| 547 | + |
|
| 548 | + $goid = $messageProps[$this->proptags['goid']]; // GlobalID (0x3) |
|
| 549 | + if (!isset($goid)) { |
|
| 550 | + return; |
|
| 551 | + } |
|
| 552 | + |
|
| 553 | + // get delegator store, if delegate is processing this cancellation |
|
| 554 | + if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 555 | + $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 556 | + |
|
| 557 | + $store = $delegatorStore['store']; |
|
| 558 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 559 | + } |
|
| 560 | + else { |
|
| 561 | + $store = $this->store; |
|
| 562 | + $calFolder = $this->openDefaultCalendar(); |
|
| 563 | + } |
|
| 564 | + |
|
| 565 | + // check for calendar access |
|
| 566 | + if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 567 | + // Throw an exception that we don't have write permissions on calendar folder, |
|
| 568 | + // allow caller to fill the error message |
|
| 569 | + throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 570 | + } |
|
| 571 | + |
|
| 572 | + $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 573 | + $basedate = $this->getBasedateFromGlobalID($goid); |
|
| 574 | + |
|
| 575 | + if ($calendarItem !== false) { |
|
| 576 | + // if basedate is provided and we could not find the item then it could be that we are processing |
|
| 577 | + // an exception so get the exception and process it |
|
| 578 | + if ($basedate) { |
|
| 579 | + $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['recurring']]); |
|
| 580 | + if ($calendarItemProps[$this->proptags['recurring']] === true) { |
|
| 581 | + $recurr = new Recurrence($store, $calendarItem); |
|
| 582 | + |
|
| 583 | + // Set message class |
|
| 584 | + $messageProps[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 585 | + |
|
| 586 | + if ($recurr->isException($basedate)) { |
|
| 587 | + $recurr->modifyException($messageProps, $basedate); |
|
| 588 | + } |
|
| 589 | + else { |
|
| 590 | + $recurr->createException($messageProps, $basedate); |
|
| 591 | + } |
|
| 592 | + } |
|
| 593 | + } |
|
| 594 | + else { |
|
| 595 | + // set the properties of the cancellation object |
|
| 596 | + mapi_setprops($calendarItem, $messageProps); |
|
| 597 | + } |
|
| 598 | + |
|
| 599 | + mapi_savechanges($calendarItem); |
|
| 600 | + } |
|
| 601 | + } |
|
| 602 | + |
|
| 603 | + /** |
|
| 604 | + * Returns true if the corresponding calendar items exists in the celendar folder for this |
|
| 605 | + * meeting request/response/cancellation. |
|
| 606 | + */ |
|
| 607 | + public function isInCalendar() { |
|
| 608 | + // @TODO check for deleted exceptions |
|
| 609 | + return $this->getCorrespondentCalendarItem(false) !== false; |
|
| 610 | + } |
|
| 611 | + |
|
| 612 | + /** |
|
| 613 | + * Accepts the meeting request by moving the item to the calendar |
|
| 614 | + * and sending a confirmation message back to the sender. If $tentative |
|
| 615 | + * is TRUE, then the item is accepted tentatively. After accepting, you |
|
| 616 | + * can't use this class instance any more. The message is closed. If you |
|
| 617 | + * specify TRUE for 'move', then the item is actually moved (from your |
|
| 618 | + * inbox probably) to the calendar. If you don't, it is copied into |
|
| 619 | + * your calendar. |
|
| 620 | + * |
|
| 621 | + * @param bool $tentative true if user as tentative accepted the meeting |
|
| 622 | + * @param bool $sendresponse true if a response has to be send to organizer |
|
| 623 | + * @param bool $move true if the meeting request should be moved to the deleted items after processing |
|
| 624 | + * @param string $newProposedStartTime contains starttime if user has proposed other time |
|
| 625 | + * @param string $newProposedEndTime contains endtime if user has proposed other time |
|
| 626 | + * @param string $basedate start of day of occurrence for which user has accepted the recurrent meeting |
|
| 627 | + * @param bool $isImported true to indicate that MR is imported from .ics or .vcs file else it false. |
|
| 628 | + * @param mixed $body |
|
| 629 | + * @param mixed $userAction |
|
| 630 | + * @param mixed $store |
|
| 631 | + * |
|
| 632 | + * @return string $entryid entryid of item which created/updated in calendar |
|
| 633 | + */ |
|
| 634 | + public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false, $isImported = false) { |
|
| 635 | + if ($this->isLocalOrganiser()) { |
|
| 636 | + return false; |
|
| 637 | + } |
|
| 638 | + |
|
| 639 | + // Remove any previous calendar items with this goid and appt id |
|
| 640 | + $messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['updatecounter'], PR_PROCESSED, PR_RCVD_REPRESENTING_ENTRYID, PR_SENDER_ENTRYID, PR_SENT_REPRESENTING_ENTRYID, PR_RECEIVED_BY_ENTRYID]); |
|
| 641 | + |
|
| 642 | + // If this meeting request is received by a delegate then open delegator's store. |
|
| 643 | + if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 644 | + $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 645 | + |
|
| 646 | + $store = $delegatorStore['store']; |
|
| 647 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 648 | + } |
|
| 649 | + else { |
|
| 650 | + $calFolder = $this->openDefaultCalendar(); |
|
| 651 | + $store = $this->store; |
|
| 652 | + } |
|
| 653 | + |
|
| 654 | + // check for calendar access |
|
| 655 | + if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 656 | + // Throw an exception that we don't have write permissions on calendar folder, |
|
| 657 | + // allow caller to fill the error message |
|
| 658 | + throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 659 | + } |
|
| 660 | + |
|
| 661 | + // if meeting is out dated then don't process it |
|
| 662 | + if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $this->isMeetingOutOfDate()) { |
|
| 663 | + return false; |
|
| 664 | + } |
|
| 665 | + |
|
| 666 | + /* |
|
| 667 | 667 | * if this function is called automatically with meeting request object then there will be |
| 668 | 668 | * two possibilitites |
| 669 | 669 | * 1) meeting request is opened first time, in this case make a tentative appointment in |
| 670 | 670 | * recipient's calendar |
| 671 | 671 | * 2) after this every subsequent request to open meeting request will not do any processing |
| 672 | 672 | */ |
| 673 | - if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction == false) { |
|
| 674 | - if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
|
| 675 | - // if meeting request is already processed then don't do anything |
|
| 676 | - return false; |
|
| 677 | - } |
|
| 678 | - |
|
| 679 | - // if correspondent calendar item is already processed then don't do anything |
|
| 680 | - $calendarItem = $this->getCorrespondentCalendarItem(); |
|
| 681 | - $calendarItemProps = mapi_getprops($calendarItem, [PR_PROCESSED]); |
|
| 682 | - if (isset($calendarItemProps[PR_PROCESSED]) && $calendarItemProps[PR_PROCESSED] == true) { |
|
| 683 | - // mark meeting-request mail as processed as well |
|
| 684 | - mapi_setprops($this->message, [PR_PROCESSED => true]); |
|
| 685 | - mapi_savechanges($this->message); |
|
| 686 | - |
|
| 687 | - return false; |
|
| 688 | - } |
|
| 689 | - } |
|
| 690 | - |
|
| 691 | - // Retrieve basedate from globalID, if it is not received as argument |
|
| 692 | - if (!$basedate) { |
|
| 693 | - $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]); |
|
| 694 | - } |
|
| 695 | - |
|
| 696 | - // set counter proposal properties in calendar item when proposing new time |
|
| 697 | - $proposeNewTimeProps = []; |
|
| 698 | - if ($newProposedStartTime && $newProposedEndTime) { |
|
| 699 | - $proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime; |
|
| 700 | - $proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime; |
|
| 701 | - $proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60; |
|
| 702 | - $proposeNewTimeProps[$this->proptags['counter_proposal']] = true; |
|
| 703 | - } |
|
| 704 | - |
|
| 705 | - // While sender is receiver then we have to process the meeting request as per the intended busy status |
|
| 706 | - // instead of tentative, and accept the same as per the intended busystatus. |
|
| 707 | - $senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID]; |
|
| 708 | - if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) { |
|
| 709 | - $entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate); |
|
| 710 | - } |
|
| 711 | - else { |
|
| 712 | - $entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate); |
|
| 713 | - } |
|
| 714 | - |
|
| 715 | - // if we have first time processed this meeting then set PR_PROCESSED property |
|
| 716 | - if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction === false && $isImported === false) { |
|
| 717 | - if (!isset($messageprops[PR_PROCESSED]) || $messageprops[PR_PROCESSED] != true) { |
|
| 718 | - // set processed flag |
|
| 719 | - mapi_setprops($this->message, [PR_PROCESSED => true]); |
|
| 720 | - mapi_savechanges($this->message); |
|
| 721 | - } |
|
| 722 | - } |
|
| 723 | - |
|
| 724 | - return $entryid; |
|
| 725 | - } |
|
| 726 | - |
|
| 727 | - public function accept($tentative, $sendresponse, $move, $proposeNewTimeProps = [], $body = false, $userAction = false, $store, $calFolder, $basedate = false) { |
|
| 728 | - $messageprops = mapi_getprops($this->message); |
|
| 729 | - $isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]); |
|
| 730 | - |
|
| 731 | - if ($sendresponse) { |
|
| 732 | - $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder); |
|
| 733 | - } |
|
| 734 | - |
|
| 735 | - /* |
|
| 673 | + if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction == false) { |
|
| 674 | + if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
|
| 675 | + // if meeting request is already processed then don't do anything |
|
| 676 | + return false; |
|
| 677 | + } |
|
| 678 | + |
|
| 679 | + // if correspondent calendar item is already processed then don't do anything |
|
| 680 | + $calendarItem = $this->getCorrespondentCalendarItem(); |
|
| 681 | + $calendarItemProps = mapi_getprops($calendarItem, [PR_PROCESSED]); |
|
| 682 | + if (isset($calendarItemProps[PR_PROCESSED]) && $calendarItemProps[PR_PROCESSED] == true) { |
|
| 683 | + // mark meeting-request mail as processed as well |
|
| 684 | + mapi_setprops($this->message, [PR_PROCESSED => true]); |
|
| 685 | + mapi_savechanges($this->message); |
|
| 686 | + |
|
| 687 | + return false; |
|
| 688 | + } |
|
| 689 | + } |
|
| 690 | + |
|
| 691 | + // Retrieve basedate from globalID, if it is not received as argument |
|
| 692 | + if (!$basedate) { |
|
| 693 | + $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]); |
|
| 694 | + } |
|
| 695 | + |
|
| 696 | + // set counter proposal properties in calendar item when proposing new time |
|
| 697 | + $proposeNewTimeProps = []; |
|
| 698 | + if ($newProposedStartTime && $newProposedEndTime) { |
|
| 699 | + $proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime; |
|
| 700 | + $proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime; |
|
| 701 | + $proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60; |
|
| 702 | + $proposeNewTimeProps[$this->proptags['counter_proposal']] = true; |
|
| 703 | + } |
|
| 704 | + |
|
| 705 | + // While sender is receiver then we have to process the meeting request as per the intended busy status |
|
| 706 | + // instead of tentative, and accept the same as per the intended busystatus. |
|
| 707 | + $senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID]; |
|
| 708 | + if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) { |
|
| 709 | + $entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate); |
|
| 710 | + } |
|
| 711 | + else { |
|
| 712 | + $entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate); |
|
| 713 | + } |
|
| 714 | + |
|
| 715 | + // if we have first time processed this meeting then set PR_PROCESSED property |
|
| 716 | + if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS]) && $userAction === false && $isImported === false) { |
|
| 717 | + if (!isset($messageprops[PR_PROCESSED]) || $messageprops[PR_PROCESSED] != true) { |
|
| 718 | + // set processed flag |
|
| 719 | + mapi_setprops($this->message, [PR_PROCESSED => true]); |
|
| 720 | + mapi_savechanges($this->message); |
|
| 721 | + } |
|
| 722 | + } |
|
| 723 | + |
|
| 724 | + return $entryid; |
|
| 725 | + } |
|
| 726 | + |
|
| 727 | + public function accept($tentative, $sendresponse, $move, $proposeNewTimeProps = [], $body = false, $userAction = false, $store, $calFolder, $basedate = false) { |
|
| 728 | + $messageprops = mapi_getprops($this->message); |
|
| 729 | + $isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]); |
|
| 730 | + |
|
| 731 | + if ($sendresponse) { |
|
| 732 | + $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder); |
|
| 733 | + } |
|
| 734 | + |
|
| 735 | + /* |
|
| 736 | 736 | * Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting. |
| 737 | 737 | * 1) If meeting req is of recurrence then we find all the occurrence in calendar because in past user might have received one or few occurrences. |
| 738 | 738 | * 2) If single occurrence then find occurrence itself using globalID and if item is not found then use cleanGlobalID to find main recurring item |
@@ -742,2394 +742,2394 @@ discard block |
||
| 742 | 742 | * 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. |
| 743 | 743 | * If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc. |
| 744 | 744 | */ |
| 745 | - if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) { |
|
| 746 | - // While processing the item mark it as read. |
|
| 747 | - mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT); |
|
| 748 | - |
|
| 749 | - // This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item. |
|
| 750 | - if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] == true) { |
|
| 751 | - $calendarItem = false; |
|
| 752 | - |
|
| 753 | - // Find main recurring item based on GlobalID (0x3) |
|
| 754 | - $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 755 | - if (is_array($items)) { |
|
| 756 | - foreach ($items as $key => $entryid) { |
|
| 757 | - $calendarItem = mapi_msgstore_openentry($store, $entryid); |
|
| 758 | - } |
|
| 759 | - } |
|
| 760 | - |
|
| 761 | - $processed = false; |
|
| 762 | - if (!$calendarItem) { |
|
| 763 | - // Recurring item not found, so create new meeting in Calendar |
|
| 764 | - $calendarItem = mapi_folder_createmessage($calFolder); |
|
| 765 | - } |
|
| 766 | - else { |
|
| 767 | - // we have found the main recurring item, check if this meeting request is already processed |
|
| 768 | - if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
|
| 769 | - // only set required properties, other properties are already copied when processing this meeting request |
|
| 770 | - // for the first time |
|
| 771 | - $processed = true; |
|
| 772 | - } |
|
| 773 | - } |
|
| 774 | - |
|
| 775 | - if (!$processed) { |
|
| 776 | - // get all the properties and copy that to calendar item |
|
| 777 | - $props = mapi_getprops($this->message); |
|
| 778 | - |
|
| 779 | - /* |
|
| 745 | + if ($this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) { |
|
| 746 | + // While processing the item mark it as read. |
|
| 747 | + mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT); |
|
| 748 | + |
|
| 749 | + // This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item. |
|
| 750 | + if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] == true) { |
|
| 751 | + $calendarItem = false; |
|
| 752 | + |
|
| 753 | + // Find main recurring item based on GlobalID (0x3) |
|
| 754 | + $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 755 | + if (is_array($items)) { |
|
| 756 | + foreach ($items as $key => $entryid) { |
|
| 757 | + $calendarItem = mapi_msgstore_openentry($store, $entryid); |
|
| 758 | + } |
|
| 759 | + } |
|
| 760 | + |
|
| 761 | + $processed = false; |
|
| 762 | + if (!$calendarItem) { |
|
| 763 | + // Recurring item not found, so create new meeting in Calendar |
|
| 764 | + $calendarItem = mapi_folder_createmessage($calFolder); |
|
| 765 | + } |
|
| 766 | + else { |
|
| 767 | + // we have found the main recurring item, check if this meeting request is already processed |
|
| 768 | + if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
|
| 769 | + // only set required properties, other properties are already copied when processing this meeting request |
|
| 770 | + // for the first time |
|
| 771 | + $processed = true; |
|
| 772 | + } |
|
| 773 | + } |
|
| 774 | + |
|
| 775 | + if (!$processed) { |
|
| 776 | + // get all the properties and copy that to calendar item |
|
| 777 | + $props = mapi_getprops($this->message); |
|
| 778 | + |
|
| 779 | + /* |
|
| 780 | 780 | * the client which has sent this meeting request can generate wrong flagdueby |
| 781 | 781 | * time (mainly OL), so regenerate that property so we will always show reminder |
| 782 | 782 | * on right time |
| 783 | 783 | */ |
| 784 | - if (isset($props[$this->proptags['reminderminutes']])) { |
|
| 785 | - $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60); |
|
| 786 | - } |
|
| 787 | - } |
|
| 788 | - else { |
|
| 789 | - // only get required properties so we will not overwrite existing updated properties from calendar |
|
| 790 | - $props = mapi_getprops($this->message, [PR_ENTRYID]); |
|
| 791 | - } |
|
| 792 | - |
|
| 793 | - // While we applying updates of MR then all local categories will be removed, |
|
| 794 | - // So get the local categories of all occurrence before applying update from organiser. |
|
| 795 | - $localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder); |
|
| 796 | - |
|
| 797 | - $props[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 798 | - // When meeting requests are generated by third-party solutions, we might be missing the updatecounter property. |
|
| 799 | - if (!isset($props[$this->proptags['updatecounter']])) { |
|
| 800 | - $props[$this->proptags['updatecounter']] = 0; |
|
| 801 | - } |
|
| 802 | - $props[$this->proptags['meetingstatus']] = olMeetingReceived; |
|
| 803 | - // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded |
|
| 804 | - $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 805 | - |
|
| 806 | - if (isset($props[$this->proptags['intendedbusystatus']])) { |
|
| 807 | - if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 808 | - $props[$this->proptags['busystatus']] = fbTentative; |
|
| 809 | - } |
|
| 810 | - else { |
|
| 811 | - $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']]; |
|
| 812 | - } |
|
| 813 | - // we already have intendedbusystatus value in $props so no need to copy it |
|
| 814 | - } |
|
| 815 | - else { |
|
| 816 | - $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 817 | - } |
|
| 818 | - |
|
| 819 | - if ($userAction) { |
|
| 820 | - $addrInfo = $this->getOwnerAddress($this->store); |
|
| 821 | - |
|
| 822 | - // if user has responded then set replytime and name |
|
| 823 | - $props[$this->proptags['replytime']] = time(); |
|
| 824 | - if (!empty($addrInfo)) { |
|
| 825 | - // @FIXME conditionally set this property only for delegation case |
|
| 826 | - $props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 827 | - } |
|
| 828 | - } |
|
| 829 | - |
|
| 830 | - mapi_setprops($calendarItem, $props); |
|
| 831 | - |
|
| 832 | - // we have already processed attachments and recipients, so no need to do it again |
|
| 833 | - if (!$processed) { |
|
| 834 | - // Copy attachments too |
|
| 835 | - $this->replaceAttachments($this->message, $calendarItem); |
|
| 836 | - // Copy recipients too |
|
| 837 | - $this->replaceRecipients($this->message, $calendarItem, $isDelegate); |
|
| 838 | - } |
|
| 839 | - |
|
| 840 | - // Find all occurrences based on CleanGlobalID (0x23) |
|
| 841 | - // there will be no exceptions left if $processed is true, but even if it doesn't hurt to recheck |
|
| 842 | - $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true); |
|
| 843 | - if (is_array($items)) { |
|
| 844 | - // Save all existing occurrence as exceptions |
|
| 845 | - foreach ($items as $entryid) { |
|
| 846 | - // Open occurrence |
|
| 847 | - $occurrenceItem = mapi_msgstore_openentry($store, $entryid); |
|
| 848 | - |
|
| 849 | - // Save occurrence into main recurring item as exception |
|
| 850 | - if ($occurrenceItem) { |
|
| 851 | - $occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]); |
|
| 852 | - |
|
| 853 | - // Find basedate of occurrence item |
|
| 854 | - $basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]); |
|
| 855 | - if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) { |
|
| 856 | - $this->mergeException($calendarItem, $occurrenceItem, $basedate, $store); |
|
| 857 | - } |
|
| 858 | - } |
|
| 859 | - } |
|
| 860 | - } |
|
| 861 | - |
|
| 862 | - mapi_savechanges($calendarItem); |
|
| 863 | - |
|
| 864 | - // After applying update of organiser all local categories of occurrence was removed, |
|
| 865 | - // So if local categories exist then apply it on respective occurrence. |
|
| 866 | - if (!empty($localCategories)) { |
|
| 867 | - $this->applyLocalCategories($calendarItem, $store, $localCategories); |
|
| 868 | - } |
|
| 869 | - |
|
| 870 | - if ($move) { |
|
| 871 | - // open wastebasket of currently logged in user and move the meeting request to it |
|
| 872 | - // for delegates this will be delegate's wastebasket folder |
|
| 873 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 874 | - mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 875 | - } |
|
| 876 | - |
|
| 877 | - $entryid = $props[PR_ENTRYID]; |
|
| 878 | - } |
|
| 879 | - else { |
|
| 880 | - /** |
|
| 881 | - * This meeting request is not recurring, so can be an exception or normal meeting. |
|
| 882 | - * If exception then find main recurring item and update exception |
|
| 883 | - * If main recurring item is not found then put exception into Calendar as normal meeting. |
|
| 884 | - */ |
|
| 885 | - $calendarItem = false; |
|
| 886 | - |
|
| 887 | - // We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence. |
|
| 888 | - if ($basedate) { |
|
| 889 | - // Find main recurring item from CleanGlobalID of this meeting request |
|
| 890 | - $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 891 | - if (is_array($items)) { |
|
| 892 | - foreach ($items as $key => $entryid) { |
|
| 893 | - $calendarItem = mapi_msgstore_openentry($store, $entryid); |
|
| 894 | - } |
|
| 895 | - } |
|
| 896 | - |
|
| 897 | - // Main recurring item is found, so now update exception |
|
| 898 | - if ($calendarItem) { |
|
| 899 | - $this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate); |
|
| 900 | - $calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]); |
|
| 901 | - $entryid = $calendarItemProps[PR_ENTRYID]; |
|
| 902 | - } |
|
| 903 | - } |
|
| 904 | - |
|
| 905 | - if (!$calendarItem) { |
|
| 906 | - $items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder); |
|
| 907 | - if (is_array($items)) { |
|
| 908 | - // Get local categories before deleting MR. |
|
| 909 | - $message = mapi_msgstore_openentry($store, $items[0]); |
|
| 910 | - $localCategories = mapi_getprops($message, [$this->proptags['categories']]); |
|
| 911 | - mapi_folder_deletemessages($calFolder, $items); |
|
| 912 | - } |
|
| 913 | - |
|
| 914 | - if ($move) { |
|
| 915 | - // All we have to do is open the default calendar, |
|
| 916 | - // set the message class correctly to be an appointment item |
|
| 917 | - // and move it to the calendar folder |
|
| 918 | - $sourcefolder = $this->openParentFolder(); |
|
| 919 | - |
|
| 920 | - // create a new calendar message, and copy the message to there, |
|
| 921 | - // since we want to delete (move to wastebasket) the original message |
|
| 922 | - $old_entryid = mapi_getprops($this->message, [PR_ENTRYID]); |
|
| 923 | - $calmsg = mapi_folder_createmessage($calFolder); |
|
| 924 | - mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */ |
|
| 925 | - |
|
| 926 | - // After creating new MR, If local categories exist then apply it on new MR. |
|
| 927 | - if (!empty($localCategories)) { |
|
| 928 | - mapi_setprops($calmsg, $localCategories); |
|
| 929 | - } |
|
| 930 | - |
|
| 931 | - $calItemProps = []; |
|
| 932 | - $calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 933 | - |
|
| 934 | - /* |
|
| 784 | + if (isset($props[$this->proptags['reminderminutes']])) { |
|
| 785 | + $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60); |
|
| 786 | + } |
|
| 787 | + } |
|
| 788 | + else { |
|
| 789 | + // only get required properties so we will not overwrite existing updated properties from calendar |
|
| 790 | + $props = mapi_getprops($this->message, [PR_ENTRYID]); |
|
| 791 | + } |
|
| 792 | + |
|
| 793 | + // While we applying updates of MR then all local categories will be removed, |
|
| 794 | + // So get the local categories of all occurrence before applying update from organiser. |
|
| 795 | + $localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder); |
|
| 796 | + |
|
| 797 | + $props[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 798 | + // When meeting requests are generated by third-party solutions, we might be missing the updatecounter property. |
|
| 799 | + if (!isset($props[$this->proptags['updatecounter']])) { |
|
| 800 | + $props[$this->proptags['updatecounter']] = 0; |
|
| 801 | + } |
|
| 802 | + $props[$this->proptags['meetingstatus']] = olMeetingReceived; |
|
| 803 | + // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded |
|
| 804 | + $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 805 | + |
|
| 806 | + if (isset($props[$this->proptags['intendedbusystatus']])) { |
|
| 807 | + if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 808 | + $props[$this->proptags['busystatus']] = fbTentative; |
|
| 809 | + } |
|
| 810 | + else { |
|
| 811 | + $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']]; |
|
| 812 | + } |
|
| 813 | + // we already have intendedbusystatus value in $props so no need to copy it |
|
| 814 | + } |
|
| 815 | + else { |
|
| 816 | + $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 817 | + } |
|
| 818 | + |
|
| 819 | + if ($userAction) { |
|
| 820 | + $addrInfo = $this->getOwnerAddress($this->store); |
|
| 821 | + |
|
| 822 | + // if user has responded then set replytime and name |
|
| 823 | + $props[$this->proptags['replytime']] = time(); |
|
| 824 | + if (!empty($addrInfo)) { |
|
| 825 | + // @FIXME conditionally set this property only for delegation case |
|
| 826 | + $props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 827 | + } |
|
| 828 | + } |
|
| 829 | + |
|
| 830 | + mapi_setprops($calendarItem, $props); |
|
| 831 | + |
|
| 832 | + // we have already processed attachments and recipients, so no need to do it again |
|
| 833 | + if (!$processed) { |
|
| 834 | + // Copy attachments too |
|
| 835 | + $this->replaceAttachments($this->message, $calendarItem); |
|
| 836 | + // Copy recipients too |
|
| 837 | + $this->replaceRecipients($this->message, $calendarItem, $isDelegate); |
|
| 838 | + } |
|
| 839 | + |
|
| 840 | + // Find all occurrences based on CleanGlobalID (0x23) |
|
| 841 | + // there will be no exceptions left if $processed is true, but even if it doesn't hurt to recheck |
|
| 842 | + $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true); |
|
| 843 | + if (is_array($items)) { |
|
| 844 | + // Save all existing occurrence as exceptions |
|
| 845 | + foreach ($items as $entryid) { |
|
| 846 | + // Open occurrence |
|
| 847 | + $occurrenceItem = mapi_msgstore_openentry($store, $entryid); |
|
| 848 | + |
|
| 849 | + // Save occurrence into main recurring item as exception |
|
| 850 | + if ($occurrenceItem) { |
|
| 851 | + $occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]); |
|
| 852 | + |
|
| 853 | + // Find basedate of occurrence item |
|
| 854 | + $basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]); |
|
| 855 | + if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) { |
|
| 856 | + $this->mergeException($calendarItem, $occurrenceItem, $basedate, $store); |
|
| 857 | + } |
|
| 858 | + } |
|
| 859 | + } |
|
| 860 | + } |
|
| 861 | + |
|
| 862 | + mapi_savechanges($calendarItem); |
|
| 863 | + |
|
| 864 | + // After applying update of organiser all local categories of occurrence was removed, |
|
| 865 | + // So if local categories exist then apply it on respective occurrence. |
|
| 866 | + if (!empty($localCategories)) { |
|
| 867 | + $this->applyLocalCategories($calendarItem, $store, $localCategories); |
|
| 868 | + } |
|
| 869 | + |
|
| 870 | + if ($move) { |
|
| 871 | + // open wastebasket of currently logged in user and move the meeting request to it |
|
| 872 | + // for delegates this will be delegate's wastebasket folder |
|
| 873 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 874 | + mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 875 | + } |
|
| 876 | + |
|
| 877 | + $entryid = $props[PR_ENTRYID]; |
|
| 878 | + } |
|
| 879 | + else { |
|
| 880 | + /** |
|
| 881 | + * This meeting request is not recurring, so can be an exception or normal meeting. |
|
| 882 | + * If exception then find main recurring item and update exception |
|
| 883 | + * If main recurring item is not found then put exception into Calendar as normal meeting. |
|
| 884 | + */ |
|
| 885 | + $calendarItem = false; |
|
| 886 | + |
|
| 887 | + // We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence. |
|
| 888 | + if ($basedate) { |
|
| 889 | + // Find main recurring item from CleanGlobalID of this meeting request |
|
| 890 | + $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 891 | + if (is_array($items)) { |
|
| 892 | + foreach ($items as $key => $entryid) { |
|
| 893 | + $calendarItem = mapi_msgstore_openentry($store, $entryid); |
|
| 894 | + } |
|
| 895 | + } |
|
| 896 | + |
|
| 897 | + // Main recurring item is found, so now update exception |
|
| 898 | + if ($calendarItem) { |
|
| 899 | + $this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate); |
|
| 900 | + $calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]); |
|
| 901 | + $entryid = $calendarItemProps[PR_ENTRYID]; |
|
| 902 | + } |
|
| 903 | + } |
|
| 904 | + |
|
| 905 | + if (!$calendarItem) { |
|
| 906 | + $items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder); |
|
| 907 | + if (is_array($items)) { |
|
| 908 | + // Get local categories before deleting MR. |
|
| 909 | + $message = mapi_msgstore_openentry($store, $items[0]); |
|
| 910 | + $localCategories = mapi_getprops($message, [$this->proptags['categories']]); |
|
| 911 | + mapi_folder_deletemessages($calFolder, $items); |
|
| 912 | + } |
|
| 913 | + |
|
| 914 | + if ($move) { |
|
| 915 | + // All we have to do is open the default calendar, |
|
| 916 | + // set the message class correctly to be an appointment item |
|
| 917 | + // and move it to the calendar folder |
|
| 918 | + $sourcefolder = $this->openParentFolder(); |
|
| 919 | + |
|
| 920 | + // create a new calendar message, and copy the message to there, |
|
| 921 | + // since we want to delete (move to wastebasket) the original message |
|
| 922 | + $old_entryid = mapi_getprops($this->message, [PR_ENTRYID]); |
|
| 923 | + $calmsg = mapi_folder_createmessage($calFolder); |
|
| 924 | + mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */ |
|
| 925 | + |
|
| 926 | + // After creating new MR, If local categories exist then apply it on new MR. |
|
| 927 | + if (!empty($localCategories)) { |
|
| 928 | + mapi_setprops($calmsg, $localCategories); |
|
| 929 | + } |
|
| 930 | + |
|
| 931 | + $calItemProps = []; |
|
| 932 | + $calItemProps[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 933 | + |
|
| 934 | + /* |
|
| 935 | 935 | * the client which has sent this meeting request can generate wrong flagdueby |
| 936 | 936 | * time (mainly OL), so regenerate that property so we will always show reminder |
| 937 | 937 | * on right time |
| 938 | 938 | */ |
| 939 | - if (isset($messageprops[$this->proptags['reminderminutes']])) { |
|
| 940 | - $calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60); |
|
| 941 | - } |
|
| 942 | - |
|
| 943 | - if (isset($messageprops[$this->proptags['intendedbusystatus']])) { |
|
| 944 | - if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 945 | - $calItemProps[$this->proptags['busystatus']] = fbTentative; |
|
| 946 | - } |
|
| 947 | - else { |
|
| 948 | - $calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 949 | - } |
|
| 950 | - $calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 951 | - } |
|
| 952 | - else { |
|
| 953 | - $calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 954 | - } |
|
| 955 | - |
|
| 956 | - // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded |
|
| 957 | - $calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 958 | - if ($userAction) { |
|
| 959 | - $addrInfo = $this->getOwnerAddress($this->store); |
|
| 960 | - |
|
| 961 | - // if user has responded then set replytime and name |
|
| 962 | - $calItemProps[$this->proptags['replytime']] = time(); |
|
| 963 | - if (!empty($addrInfo)) { |
|
| 964 | - $calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 965 | - } |
|
| 966 | - } |
|
| 967 | - |
|
| 968 | - mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps); |
|
| 969 | - |
|
| 970 | - // get properties which stores owner information in meeting request mails |
|
| 971 | - $props = mapi_getprops($calmsg, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]); |
|
| 972 | - |
|
| 973 | - // add owner to recipient table |
|
| 974 | - $recips = []; |
|
| 975 | - $this->addOrganizer($props, $recips); |
|
| 976 | - mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips); |
|
| 977 | - mapi_savechanges($calmsg); |
|
| 978 | - |
|
| 979 | - // Move the message to the wastebasket |
|
| 980 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 981 | - mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 982 | - |
|
| 983 | - $messageprops = mapi_getprops($calmsg, [PR_ENTRYID]); |
|
| 984 | - $entryid = $messageprops[PR_ENTRYID]; |
|
| 985 | - } |
|
| 986 | - else { |
|
| 987 | - // Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment |
|
| 988 | - $new = mapi_folder_createmessage($calFolder); |
|
| 989 | - $props = mapi_getprops($this->message); |
|
| 990 | - |
|
| 991 | - $props[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 992 | - |
|
| 993 | - // After creating new MR, If local categories exist then apply it on new MR. |
|
| 994 | - if (!empty($localCategories)) { |
|
| 995 | - mapi_setprops($new, $localCategories); |
|
| 996 | - } |
|
| 997 | - |
|
| 998 | - /* |
|
| 939 | + if (isset($messageprops[$this->proptags['reminderminutes']])) { |
|
| 940 | + $calItemProps[$this->proptags['flagdueby']] = $messageprops[$this->proptags['startdate']] - ($messageprops[$this->proptags['reminderminutes']] * 60); |
|
| 941 | + } |
|
| 942 | + |
|
| 943 | + if (isset($messageprops[$this->proptags['intendedbusystatus']])) { |
|
| 944 | + if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 945 | + $calItemProps[$this->proptags['busystatus']] = fbTentative; |
|
| 946 | + } |
|
| 947 | + else { |
|
| 948 | + $calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 949 | + } |
|
| 950 | + $calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 951 | + } |
|
| 952 | + else { |
|
| 953 | + $calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 954 | + } |
|
| 955 | + |
|
| 956 | + // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded |
|
| 957 | + $calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 958 | + if ($userAction) { |
|
| 959 | + $addrInfo = $this->getOwnerAddress($this->store); |
|
| 960 | + |
|
| 961 | + // if user has responded then set replytime and name |
|
| 962 | + $calItemProps[$this->proptags['replytime']] = time(); |
|
| 963 | + if (!empty($addrInfo)) { |
|
| 964 | + $calItemProps[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 965 | + } |
|
| 966 | + } |
|
| 967 | + |
|
| 968 | + mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps); |
|
| 969 | + |
|
| 970 | + // get properties which stores owner information in meeting request mails |
|
| 971 | + $props = mapi_getprops($calmsg, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]); |
|
| 972 | + |
|
| 973 | + // add owner to recipient table |
|
| 974 | + $recips = []; |
|
| 975 | + $this->addOrganizer($props, $recips); |
|
| 976 | + mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips); |
|
| 977 | + mapi_savechanges($calmsg); |
|
| 978 | + |
|
| 979 | + // Move the message to the wastebasket |
|
| 980 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 981 | + mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 982 | + |
|
| 983 | + $messageprops = mapi_getprops($calmsg, [PR_ENTRYID]); |
|
| 984 | + $entryid = $messageprops[PR_ENTRYID]; |
|
| 985 | + } |
|
| 986 | + else { |
|
| 987 | + // Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment |
|
| 988 | + $new = mapi_folder_createmessage($calFolder); |
|
| 989 | + $props = mapi_getprops($this->message); |
|
| 990 | + |
|
| 991 | + $props[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 992 | + |
|
| 993 | + // After creating new MR, If local categories exist then apply it on new MR. |
|
| 994 | + if (!empty($localCategories)) { |
|
| 995 | + mapi_setprops($new, $localCategories); |
|
| 996 | + } |
|
| 997 | + |
|
| 998 | + /* |
|
| 999 | 999 | * the client which has sent this meeting request can generate wrong flagdueby |
| 1000 | 1000 | * time (mainly OL), so regenerate that property so we will always show reminder |
| 1001 | 1001 | * on right time |
| 1002 | 1002 | */ |
| 1003 | - if (isset($props[$this->proptags['reminderminutes']])) { |
|
| 1004 | - $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60); |
|
| 1005 | - } |
|
| 1006 | - |
|
| 1007 | - // When meeting requests are generated by third-party solutions, we might be missing the properties. |
|
| 1008 | - if (!isset($props[$this->proptags['updatecounter']])) { |
|
| 1009 | - $props[$this->proptags['updatecounter']] = 0; |
|
| 1010 | - } |
|
| 1011 | - if (!isset($props[$this->proptags['recurring']])) { |
|
| 1012 | - $props[$this->proptags['recurring']] = false; |
|
| 1013 | - } |
|
| 1014 | - |
|
| 1015 | - // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded |
|
| 1016 | - $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 1017 | - |
|
| 1018 | - if (isset($props[$this->proptags['intendedbusystatus']])) { |
|
| 1019 | - if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 1020 | - $props[$this->proptags['busystatus']] = fbTentative; |
|
| 1021 | - } |
|
| 1022 | - else { |
|
| 1023 | - $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']]; |
|
| 1024 | - } |
|
| 1025 | - // we already have intendedbusystatus value in $props so no need to copy it |
|
| 1026 | - } |
|
| 1027 | - else { |
|
| 1028 | - $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 1029 | - } |
|
| 1030 | - |
|
| 1031 | - if ($userAction) { |
|
| 1032 | - $addrInfo = $this->getOwnerAddress($this->store); |
|
| 1033 | - |
|
| 1034 | - // if user has responded then set replytime and name |
|
| 1035 | - $props[$this->proptags['replytime']] = time(); |
|
| 1036 | - if (!empty($addrInfo)) { |
|
| 1037 | - $props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 1038 | - } |
|
| 1039 | - } |
|
| 1040 | - |
|
| 1041 | - mapi_setprops($new, $proposeNewTimeProps + $props); |
|
| 1042 | - |
|
| 1043 | - // Copy attachments too |
|
| 1044 | - $this->replaceAttachments($this->message, $new); |
|
| 1045 | - |
|
| 1046 | - // get recipient table of source message |
|
| 1047 | - $recipientTable = mapi_message_getrecipienttable($this->message); |
|
| 1048 | - |
|
| 1049 | - // If delegate, then do not add the delegate in recipients |
|
| 1050 | - if ($isDelegate) { |
|
| 1051 | - $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]); |
|
| 1052 | - $res = [RES_PROPERTY, [ |
|
| 1053 | - RELOP => RELOP_NE, |
|
| 1054 | - ULPROPTAG => PR_EMAIL_ADDRESS, |
|
| 1055 | - VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]], |
|
| 1056 | - ], |
|
| 1057 | - ]; |
|
| 1058 | - $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res); |
|
| 1059 | - } |
|
| 1060 | - else { |
|
| 1061 | - $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 1062 | - } |
|
| 1063 | - $this->addOrganizer($props, $recipients); |
|
| 1064 | - mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients); |
|
| 1065 | - mapi_savechanges($new); |
|
| 1066 | - |
|
| 1067 | - $props = mapi_getprops($new, [PR_ENTRYID]); |
|
| 1068 | - $entryid = $props[PR_ENTRYID]; |
|
| 1069 | - } |
|
| 1070 | - } |
|
| 1071 | - } |
|
| 1072 | - } |
|
| 1073 | - else { |
|
| 1074 | - // Here only properties are set on calendaritem, because user is responding from calendar. |
|
| 1075 | - $props = []; |
|
| 1076 | - $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted; |
|
| 1077 | - |
|
| 1078 | - if (isset($messageprops[$this->proptags['intendedbusystatus']])) { |
|
| 1079 | - if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 1080 | - $props[$this->proptags['busystatus']] = fbTentative; |
|
| 1081 | - } |
|
| 1082 | - else { |
|
| 1083 | - $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 1084 | - } |
|
| 1085 | - $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 1086 | - } |
|
| 1087 | - else { |
|
| 1088 | - $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 1089 | - } |
|
| 1090 | - |
|
| 1091 | - $props[$this->proptags['meetingstatus']] = olMeetingReceived; |
|
| 1092 | - |
|
| 1093 | - $addrInfo = $this->getOwnerAddress($this->store); |
|
| 1094 | - |
|
| 1095 | - // if user has responded then set replytime and name |
|
| 1096 | - $props[$this->proptags['replytime']] = time(); |
|
| 1097 | - if (!empty($addrInfo)) { |
|
| 1098 | - $props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 1099 | - } |
|
| 1100 | - |
|
| 1101 | - if ($basedate) { |
|
| 1102 | - $recurr = new Recurrence($store, $this->message); |
|
| 1103 | - |
|
| 1104 | - // Copy recipients list |
|
| 1105 | - $reciptable = mapi_message_getrecipienttable($this->message); |
|
| 1106 | - $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 1107 | - |
|
| 1108 | - if ($recurr->isException($basedate)) { |
|
| 1109 | - $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips); |
|
| 1110 | - } |
|
| 1111 | - else { |
|
| 1112 | - $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
|
| 1113 | - $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
|
| 1114 | - |
|
| 1115 | - $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 1116 | - $props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME]; |
|
| 1117 | - $props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 1118 | - $props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 1119 | - $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 1120 | - |
|
| 1121 | - $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips); |
|
| 1122 | - } |
|
| 1123 | - } |
|
| 1124 | - else { |
|
| 1125 | - mapi_setprops($this->message, $proposeNewTimeProps + $props); |
|
| 1126 | - } |
|
| 1127 | - mapi_savechanges($this->message); |
|
| 1128 | - |
|
| 1129 | - $entryid = $messageprops[PR_ENTRYID]; |
|
| 1130 | - } |
|
| 1131 | - |
|
| 1132 | - return $entryid; |
|
| 1133 | - } |
|
| 1134 | - |
|
| 1135 | - /** |
|
| 1136 | - * Declines the meeting request by moving the item to the deleted |
|
| 1137 | - * items folder and sending a decline message. After declining, you |
|
| 1138 | - * can't use this class instance any more. The message is closed. |
|
| 1139 | - * When an occurrence is decline then false is returned because that |
|
| 1140 | - * occurrence is deleted not the recurring item. |
|
| 1141 | - * |
|
| 1142 | - * @param bool $sendresponse true if a response has to be sent to organizer |
|
| 1143 | - * @param string $basedate if specified contains starttime of day of an occurrence |
|
| 1144 | - * @param mixed $body |
|
| 1145 | - * |
|
| 1146 | - * @return bool true if item is deleted from Calendar else false |
|
| 1147 | - */ |
|
| 1148 | - public function doDecline($sendresponse, $basedate = false, $body = false) { |
|
| 1149 | - if ($this->isLocalOrganiser()) { |
|
| 1150 | - return false; |
|
| 1151 | - } |
|
| 1152 | - |
|
| 1153 | - $result = false; |
|
| 1154 | - $calendaritem = false; |
|
| 1155 | - |
|
| 1156 | - // Remove any previous calendar items with this goid and appt id |
|
| 1157 | - $messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 1158 | - |
|
| 1159 | - // If this meeting request is received by a delegate then open delegator's store. |
|
| 1160 | - if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 1161 | - $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 1162 | - |
|
| 1163 | - $store = $delegatorStore['store']; |
|
| 1164 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 1165 | - } |
|
| 1166 | - else { |
|
| 1167 | - $calFolder = $this->openDefaultCalendar(); |
|
| 1168 | - $store = $this->store; |
|
| 1169 | - } |
|
| 1170 | - |
|
| 1171 | - // check for calendar access before deleting the calendar item |
|
| 1172 | - if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 1173 | - // Throw an exception that we don't have write permissions on calendar folder, |
|
| 1174 | - // allow caller to fill the error message |
|
| 1175 | - throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 1176 | - } |
|
| 1177 | - |
|
| 1178 | - $goid = $messageprops[$this->proptags['goid']]; |
|
| 1179 | - |
|
| 1180 | - // First, find the items in the calendar by GlobalObjid (0x3) |
|
| 1181 | - $entryids = $this->findCalendarItems($goid, $calFolder); |
|
| 1182 | - |
|
| 1183 | - if (!$basedate) { |
|
| 1184 | - $basedate = $this->getBasedateFromGlobalID($goid); |
|
| 1185 | - } |
|
| 1186 | - |
|
| 1187 | - if ($sendresponse) { |
|
| 1188 | - $this->createResponse(olResponseDeclined, [], $body, $store, $basedate, $calFolder); |
|
| 1189 | - } |
|
| 1190 | - |
|
| 1191 | - if ($basedate) { |
|
| 1192 | - // use CleanGlobalObjid (0x23) |
|
| 1193 | - $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 1194 | - |
|
| 1195 | - if (is_array($calendaritems)) { |
|
| 1196 | - foreach ($calendaritems as $entryid) { |
|
| 1197 | - // Open each calendar item and set the properties of the cancellation object |
|
| 1198 | - $calendaritem = mapi_msgstore_openentry($store, $entryid); |
|
| 1199 | - |
|
| 1200 | - // Recurring item is found, now delete exception |
|
| 1201 | - if ($calendaritem) { |
|
| 1202 | - $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store); |
|
| 1203 | - $result = true; |
|
| 1204 | - } |
|
| 1205 | - } |
|
| 1206 | - } |
|
| 1207 | - |
|
| 1208 | - if ($this->isMeetingRequest()) { |
|
| 1209 | - $calendaritem = false; |
|
| 1210 | - } |
|
| 1211 | - } |
|
| 1212 | - |
|
| 1213 | - if (!$calendaritem) { |
|
| 1214 | - $calendar = $this->openDefaultCalendar($store); |
|
| 1215 | - |
|
| 1216 | - if (!empty($entryids)) { |
|
| 1217 | - mapi_folder_deletemessages($calendar, $entryids); |
|
| 1218 | - } |
|
| 1219 | - |
|
| 1220 | - // All we have to do to decline, is to move the item to the waste basket |
|
| 1221 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 1222 | - $sourcefolder = $this->openParentFolder(); |
|
| 1223 | - |
|
| 1224 | - $messageprops = mapi_getprops($this->message, [PR_ENTRYID]); |
|
| 1225 | - |
|
| 1226 | - // Release the message |
|
| 1227 | - $this->message = null; |
|
| 1228 | - |
|
| 1229 | - // Move the message to the waste basket |
|
| 1230 | - mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1231 | - |
|
| 1232 | - $result = true; |
|
| 1233 | - } |
|
| 1234 | - |
|
| 1235 | - return $result; |
|
| 1236 | - } |
|
| 1237 | - |
|
| 1238 | - /** |
|
| 1239 | - * Removes a meeting request from the calendar when the user presses the |
|
| 1240 | - * 'remove from calendar' button in response to a meeting cancellation. |
|
| 1241 | - * |
|
| 1242 | - * @param string $basedate if specified contains starttime of day of an occurrence |
|
| 1243 | - */ |
|
| 1244 | - public function doRemoveFromCalendar($basedate) { |
|
| 1245 | - if ($this->isLocalOrganiser()) { |
|
| 1246 | - return false; |
|
| 1247 | - } |
|
| 1248 | - |
|
| 1249 | - $messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]); |
|
| 1250 | - |
|
| 1251 | - $goid = $messageprops[$this->proptags['goid']]; |
|
| 1252 | - |
|
| 1253 | - if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 1254 | - $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 1255 | - |
|
| 1256 | - $store = $delegatorStore['store']; |
|
| 1257 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 1258 | - } |
|
| 1259 | - else { |
|
| 1260 | - $store = $this->store; |
|
| 1261 | - $calFolder = $this->openDefaultCalendar(); |
|
| 1262 | - } |
|
| 1263 | - |
|
| 1264 | - // check for calendar access before deleting the calendar item |
|
| 1265 | - if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 1266 | - // Throw an exception that we don't have write permissions on calendar folder, |
|
| 1267 | - // allow caller to fill the error message |
|
| 1268 | - throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 1269 | - } |
|
| 1270 | - |
|
| 1271 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 1272 | - // get the source folder of the meeting message |
|
| 1273 | - $sourcefolder = $this->openParentFolder(); |
|
| 1274 | - |
|
| 1275 | - // Check if the message is a meeting request in the inbox or a calendaritem by checking the message class |
|
| 1276 | - if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) { |
|
| 1277 | - // get the basedate to check for exception |
|
| 1278 | - $basedate = $this->getBasedateFromGlobalID($goid); |
|
| 1279 | - |
|
| 1280 | - $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 1281 | - |
|
| 1282 | - if ($calendarItem !== false) { |
|
| 1283 | - // basedate is provided so open exception |
|
| 1284 | - if ($basedate) { |
|
| 1285 | - $exception = $this->getExceptionItem($calendarItem, $basedate); |
|
| 1286 | - |
|
| 1287 | - if ($exception !== false) { |
|
| 1288 | - // exception found, remove it from calendar |
|
| 1289 | - $this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store); |
|
| 1290 | - } |
|
| 1291 | - } |
|
| 1292 | - else { |
|
| 1293 | - // remove normal / recurring series from calendar |
|
| 1294 | - $entryids = mapi_getprops($calendarItem, [PR_ENTRYID]); |
|
| 1295 | - |
|
| 1296 | - $entryids = [$entryids[PR_ENTRYID]]; |
|
| 1297 | - |
|
| 1298 | - mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE); |
|
| 1299 | - } |
|
| 1300 | - } |
|
| 1301 | - |
|
| 1302 | - // Release the message, because we are going to move it to wastebasket |
|
| 1303 | - $this->message = null; |
|
| 1304 | - |
|
| 1305 | - // Move the cancellation mail to wastebasket |
|
| 1306 | - mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1307 | - } |
|
| 1308 | - else { |
|
| 1309 | - // Here only properties are set on calendaritem, because user is responding from calendar. |
|
| 1310 | - if ($basedate) { |
|
| 1311 | - // remove the occurrence |
|
| 1312 | - $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store); |
|
| 1313 | - } |
|
| 1314 | - else { |
|
| 1315 | - // remove normal/recurring meeting item. |
|
| 1316 | - // Move the message to the waste basket |
|
| 1317 | - mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1318 | - } |
|
| 1319 | - } |
|
| 1320 | - } |
|
| 1321 | - |
|
| 1322 | - /** |
|
| 1323 | - * Function can be used to cancel any existing meeting and send cancellation mails to attendees. |
|
| 1324 | - * Should only be called from meeting object from calendar. |
|
| 1325 | - * |
|
| 1326 | - * @param string $basedate (optional) basedate of occurrence which should be cancelled |
|
| 1327 | - * @FIXME cancellation mail is also sent to attendee which has declined the meeting |
|
| 1328 | - * @FIXME don't send canellation mail when cancelling meeting from past |
|
| 1329 | - */ |
|
| 1330 | - public function doCancelInvitation($basedate = false) { |
|
| 1331 | - if (!$this->isLocalOrganiser()) { |
|
| 1332 | - return; |
|
| 1333 | - } |
|
| 1334 | - |
|
| 1335 | - // check write access for delegate |
|
| 1336 | - if ($this->checkCalendarWriteAccess($this->store) !== true) { |
|
| 1337 | - // Throw an exception that we don't have write permissions on calendar folder, |
|
| 1338 | - // error message will be filled by module |
|
| 1339 | - throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 1340 | - } |
|
| 1341 | - |
|
| 1342 | - $messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]); |
|
| 1343 | - |
|
| 1344 | - if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) { |
|
| 1345 | - // cancellation of recurring series or one occurrence |
|
| 1346 | - $recurrence = new Recurrence($this->store, $this->message); |
|
| 1347 | - |
|
| 1348 | - // if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence |
|
| 1349 | - if ($basedate) { |
|
| 1350 | - $recurrence->createException([], $basedate, true); |
|
| 1351 | - } |
|
| 1352 | - |
|
| 1353 | - // update the meeting request |
|
| 1354 | - $this->updateMeetingRequest(); |
|
| 1355 | - |
|
| 1356 | - // send cancellation mails |
|
| 1357 | - $this->sendMeetingRequest(true, _('Canceled: '), $basedate); |
|
| 1358 | - |
|
| 1359 | - // save changes in the message |
|
| 1360 | - mapi_savechanges($this->message); |
|
| 1361 | - } |
|
| 1362 | - else { |
|
| 1363 | - // cancellation of normal meeting request |
|
| 1364 | - // Send the cancellation |
|
| 1365 | - $this->updateMeetingRequest(); |
|
| 1366 | - $this->sendMeetingRequest(true, _('Canceled: ')); |
|
| 1367 | - |
|
| 1368 | - // save changes in the message |
|
| 1369 | - mapi_savechanges($this->message); |
|
| 1370 | - } |
|
| 1371 | - |
|
| 1372 | - // if basedate is specified then we have already created exception of it so nothing should be done now |
|
| 1373 | - // but when cancelling normal / recurring meeting request we need to remove meeting from calendar |
|
| 1374 | - if ($basedate === false) { |
|
| 1375 | - // get the wastebasket folder, for delegate this will give wastebasket of delegate |
|
| 1376 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 1377 | - |
|
| 1378 | - // get the source folder of the meeting message |
|
| 1379 | - $sourcefolder = $this->openParentFolder(); |
|
| 1380 | - |
|
| 1381 | - // Move the message to the deleted items |
|
| 1382 | - mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1383 | - } |
|
| 1384 | - } |
|
| 1385 | - |
|
| 1386 | - /** |
|
| 1387 | - * Convert epoch to MAPI FileTime, number of 100-nanosecond units since |
|
| 1388 | - * the start of January 1, 1601. |
|
| 1389 | - * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx. |
|
| 1390 | - * |
|
| 1391 | - * @param int the current epoch |
|
| 1392 | - * @param mixed $epoch |
|
| 1393 | - * |
|
| 1394 | - * @return the MAPI FileTime equalevent to the given epoch time |
|
| 1395 | - */ |
|
| 1396 | - public function epochToMapiFileTime($epoch) { |
|
| 1397 | - $nanoseconds_between_epoch = 116444736000000000; |
|
| 1398 | - |
|
| 1399 | - return ($epoch * 10000000) + $nanoseconds_between_epoch; |
|
| 1400 | - } |
|
| 1401 | - |
|
| 1402 | - /** |
|
| 1403 | - * Sets the properties in the message so that is can be sent |
|
| 1404 | - * as a meeting request. The caller has to submit the message. This |
|
| 1405 | - * is only used for new MeetingRequests. Pass the appointment item as $message |
|
| 1406 | - * in the constructor to do this. |
|
| 1407 | - * |
|
| 1408 | - * @param mixed $basedate |
|
| 1409 | - */ |
|
| 1410 | - public function setMeetingRequest($basedate = false) { |
|
| 1411 | - $props = mapi_getprops($this->message, [$this->proptags['updatecounter']]); |
|
| 1412 | - |
|
| 1413 | - // Create a new global id for this item |
|
| 1414 | - // https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx |
|
| 1415 | - $goid = pack('H*', '040000008200E00074C5B7101A82E00800000000'); |
|
| 1416 | - // Creation Time |
|
| 1417 | - $time = $this->epochToMapiFileTime(time()); |
|
| 1418 | - $highdatetime = $time >> 32; |
|
| 1419 | - $lowdatetime = $time & 0xFFFFFFFF; |
|
| 1420 | - $goid .= pack('II', $lowdatetime, $highdatetime); |
|
| 1421 | - // 8 Zeros |
|
| 1422 | - $goid .= pack('P', 0); |
|
| 1423 | - // Length of the random data |
|
| 1424 | - $goid .= pack('V', 16); |
|
| 1425 | - // Random data. |
|
| 1426 | - for ($i = 0; $i < 16; ++$i) { |
|
| 1427 | - $goid .= chr(rand(0, 255)); |
|
| 1428 | - } |
|
| 1429 | - |
|
| 1430 | - // Create a new appointment id for this item |
|
| 1431 | - $apptid = rand(); |
|
| 1432 | - |
|
| 1433 | - $props[PR_OWNER_APPT_ID] = $apptid; |
|
| 1434 | - $props[PR_ICON_INDEX] = 1026; |
|
| 1435 | - $props[$this->proptags['goid']] = $goid; |
|
| 1436 | - $props[$this->proptags['goid2']] = $goid; |
|
| 1437 | - |
|
| 1438 | - if (!isset($props[$this->proptags['updatecounter']])) { |
|
| 1439 | - $props[$this->proptags['updatecounter']] = 0; // OL also starts sequence no with zero. |
|
| 1440 | - $props[$this->proptags['last_updatecounter']] = 0; |
|
| 1441 | - } |
|
| 1442 | - |
|
| 1443 | - mapi_setprops($this->message, $props); |
|
| 1444 | - } |
|
| 1445 | - |
|
| 1446 | - /** |
|
| 1447 | - * Sends a meeting request by copying it to the outbox, converting |
|
| 1448 | - * the message class, adding some properties that are required only |
|
| 1449 | - * for sending the message and submitting the message. Set cancel to |
|
| 1450 | - * true if you wish to completely cancel the meeting request. You can |
|
| 1451 | - * specify an optional 'prefix' to prefix the sent message, which is normally |
|
| 1452 | - * 'Canceled: '. |
|
| 1453 | - * |
|
| 1454 | - * @param mixed $cancel |
|
| 1455 | - * @param mixed $prefix |
|
| 1456 | - * @param mixed $basedate |
|
| 1457 | - * @param mixed $modifiedRecips |
|
| 1458 | - * @param mixed $deletedRecips |
|
| 1459 | - */ |
|
| 1460 | - public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false) { |
|
| 1461 | - $this->includesResources = false; |
|
| 1462 | - $this->nonAcceptingResources = []; |
|
| 1463 | - |
|
| 1464 | - // Get the properties of the message |
|
| 1465 | - $messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]); |
|
| 1466 | - |
|
| 1467 | - /* |
|
| 1003 | + if (isset($props[$this->proptags['reminderminutes']])) { |
|
| 1004 | + $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60); |
|
| 1005 | + } |
|
| 1006 | + |
|
| 1007 | + // When meeting requests are generated by third-party solutions, we might be missing the properties. |
|
| 1008 | + if (!isset($props[$this->proptags['updatecounter']])) { |
|
| 1009 | + $props[$this->proptags['updatecounter']] = 0; |
|
| 1010 | + } |
|
| 1011 | + if (!isset($props[$this->proptags['recurring']])) { |
|
| 1012 | + $props[$this->proptags['recurring']] = false; |
|
| 1013 | + } |
|
| 1014 | + |
|
| 1015 | + // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded |
|
| 1016 | + $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 1017 | + |
|
| 1018 | + if (isset($props[$this->proptags['intendedbusystatus']])) { |
|
| 1019 | + if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 1020 | + $props[$this->proptags['busystatus']] = fbTentative; |
|
| 1021 | + } |
|
| 1022 | + else { |
|
| 1023 | + $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']]; |
|
| 1024 | + } |
|
| 1025 | + // we already have intendedbusystatus value in $props so no need to copy it |
|
| 1026 | + } |
|
| 1027 | + else { |
|
| 1028 | + $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 1029 | + } |
|
| 1030 | + |
|
| 1031 | + if ($userAction) { |
|
| 1032 | + $addrInfo = $this->getOwnerAddress($this->store); |
|
| 1033 | + |
|
| 1034 | + // if user has responded then set replytime and name |
|
| 1035 | + $props[$this->proptags['replytime']] = time(); |
|
| 1036 | + if (!empty($addrInfo)) { |
|
| 1037 | + $props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 1038 | + } |
|
| 1039 | + } |
|
| 1040 | + |
|
| 1041 | + mapi_setprops($new, $proposeNewTimeProps + $props); |
|
| 1042 | + |
|
| 1043 | + // Copy attachments too |
|
| 1044 | + $this->replaceAttachments($this->message, $new); |
|
| 1045 | + |
|
| 1046 | + // get recipient table of source message |
|
| 1047 | + $recipientTable = mapi_message_getrecipienttable($this->message); |
|
| 1048 | + |
|
| 1049 | + // If delegate, then do not add the delegate in recipients |
|
| 1050 | + if ($isDelegate) { |
|
| 1051 | + $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]); |
|
| 1052 | + $res = [RES_PROPERTY, [ |
|
| 1053 | + RELOP => RELOP_NE, |
|
| 1054 | + ULPROPTAG => PR_EMAIL_ADDRESS, |
|
| 1055 | + VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]], |
|
| 1056 | + ], |
|
| 1057 | + ]; |
|
| 1058 | + $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res); |
|
| 1059 | + } |
|
| 1060 | + else { |
|
| 1061 | + $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 1062 | + } |
|
| 1063 | + $this->addOrganizer($props, $recipients); |
|
| 1064 | + mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients); |
|
| 1065 | + mapi_savechanges($new); |
|
| 1066 | + |
|
| 1067 | + $props = mapi_getprops($new, [PR_ENTRYID]); |
|
| 1068 | + $entryid = $props[PR_ENTRYID]; |
|
| 1069 | + } |
|
| 1070 | + } |
|
| 1071 | + } |
|
| 1072 | + } |
|
| 1073 | + else { |
|
| 1074 | + // Here only properties are set on calendaritem, because user is responding from calendar. |
|
| 1075 | + $props = []; |
|
| 1076 | + $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted; |
|
| 1077 | + |
|
| 1078 | + if (isset($messageprops[$this->proptags['intendedbusystatus']])) { |
|
| 1079 | + if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 1080 | + $props[$this->proptags['busystatus']] = fbTentative; |
|
| 1081 | + } |
|
| 1082 | + else { |
|
| 1083 | + $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 1084 | + } |
|
| 1085 | + $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
|
| 1086 | + } |
|
| 1087 | + else { |
|
| 1088 | + $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 1089 | + } |
|
| 1090 | + |
|
| 1091 | + $props[$this->proptags['meetingstatus']] = olMeetingReceived; |
|
| 1092 | + |
|
| 1093 | + $addrInfo = $this->getOwnerAddress($this->store); |
|
| 1094 | + |
|
| 1095 | + // if user has responded then set replytime and name |
|
| 1096 | + $props[$this->proptags['replytime']] = time(); |
|
| 1097 | + if (!empty($addrInfo)) { |
|
| 1098 | + $props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 1099 | + } |
|
| 1100 | + |
|
| 1101 | + if ($basedate) { |
|
| 1102 | + $recurr = new Recurrence($store, $this->message); |
|
| 1103 | + |
|
| 1104 | + // Copy recipients list |
|
| 1105 | + $reciptable = mapi_message_getrecipienttable($this->message); |
|
| 1106 | + $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 1107 | + |
|
| 1108 | + if ($recurr->isException($basedate)) { |
|
| 1109 | + $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips); |
|
| 1110 | + } |
|
| 1111 | + else { |
|
| 1112 | + $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
|
| 1113 | + $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
|
| 1114 | + |
|
| 1115 | + $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 1116 | + $props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME]; |
|
| 1117 | + $props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 1118 | + $props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 1119 | + $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 1120 | + |
|
| 1121 | + $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips); |
|
| 1122 | + } |
|
| 1123 | + } |
|
| 1124 | + else { |
|
| 1125 | + mapi_setprops($this->message, $proposeNewTimeProps + $props); |
|
| 1126 | + } |
|
| 1127 | + mapi_savechanges($this->message); |
|
| 1128 | + |
|
| 1129 | + $entryid = $messageprops[PR_ENTRYID]; |
|
| 1130 | + } |
|
| 1131 | + |
|
| 1132 | + return $entryid; |
|
| 1133 | + } |
|
| 1134 | + |
|
| 1135 | + /** |
|
| 1136 | + * Declines the meeting request by moving the item to the deleted |
|
| 1137 | + * items folder and sending a decline message. After declining, you |
|
| 1138 | + * can't use this class instance any more. The message is closed. |
|
| 1139 | + * When an occurrence is decline then false is returned because that |
|
| 1140 | + * occurrence is deleted not the recurring item. |
|
| 1141 | + * |
|
| 1142 | + * @param bool $sendresponse true if a response has to be sent to organizer |
|
| 1143 | + * @param string $basedate if specified contains starttime of day of an occurrence |
|
| 1144 | + * @param mixed $body |
|
| 1145 | + * |
|
| 1146 | + * @return bool true if item is deleted from Calendar else false |
|
| 1147 | + */ |
|
| 1148 | + public function doDecline($sendresponse, $basedate = false, $body = false) { |
|
| 1149 | + if ($this->isLocalOrganiser()) { |
|
| 1150 | + return false; |
|
| 1151 | + } |
|
| 1152 | + |
|
| 1153 | + $result = false; |
|
| 1154 | + $calendaritem = false; |
|
| 1155 | + |
|
| 1156 | + // Remove any previous calendar items with this goid and appt id |
|
| 1157 | + $messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 1158 | + |
|
| 1159 | + // If this meeting request is received by a delegate then open delegator's store. |
|
| 1160 | + if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 1161 | + $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 1162 | + |
|
| 1163 | + $store = $delegatorStore['store']; |
|
| 1164 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 1165 | + } |
|
| 1166 | + else { |
|
| 1167 | + $calFolder = $this->openDefaultCalendar(); |
|
| 1168 | + $store = $this->store; |
|
| 1169 | + } |
|
| 1170 | + |
|
| 1171 | + // check for calendar access before deleting the calendar item |
|
| 1172 | + if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 1173 | + // Throw an exception that we don't have write permissions on calendar folder, |
|
| 1174 | + // allow caller to fill the error message |
|
| 1175 | + throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 1176 | + } |
|
| 1177 | + |
|
| 1178 | + $goid = $messageprops[$this->proptags['goid']]; |
|
| 1179 | + |
|
| 1180 | + // First, find the items in the calendar by GlobalObjid (0x3) |
|
| 1181 | + $entryids = $this->findCalendarItems($goid, $calFolder); |
|
| 1182 | + |
|
| 1183 | + if (!$basedate) { |
|
| 1184 | + $basedate = $this->getBasedateFromGlobalID($goid); |
|
| 1185 | + } |
|
| 1186 | + |
|
| 1187 | + if ($sendresponse) { |
|
| 1188 | + $this->createResponse(olResponseDeclined, [], $body, $store, $basedate, $calFolder); |
|
| 1189 | + } |
|
| 1190 | + |
|
| 1191 | + if ($basedate) { |
|
| 1192 | + // use CleanGlobalObjid (0x23) |
|
| 1193 | + $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 1194 | + |
|
| 1195 | + if (is_array($calendaritems)) { |
|
| 1196 | + foreach ($calendaritems as $entryid) { |
|
| 1197 | + // Open each calendar item and set the properties of the cancellation object |
|
| 1198 | + $calendaritem = mapi_msgstore_openentry($store, $entryid); |
|
| 1199 | + |
|
| 1200 | + // Recurring item is found, now delete exception |
|
| 1201 | + if ($calendaritem) { |
|
| 1202 | + $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store); |
|
| 1203 | + $result = true; |
|
| 1204 | + } |
|
| 1205 | + } |
|
| 1206 | + } |
|
| 1207 | + |
|
| 1208 | + if ($this->isMeetingRequest()) { |
|
| 1209 | + $calendaritem = false; |
|
| 1210 | + } |
|
| 1211 | + } |
|
| 1212 | + |
|
| 1213 | + if (!$calendaritem) { |
|
| 1214 | + $calendar = $this->openDefaultCalendar($store); |
|
| 1215 | + |
|
| 1216 | + if (!empty($entryids)) { |
|
| 1217 | + mapi_folder_deletemessages($calendar, $entryids); |
|
| 1218 | + } |
|
| 1219 | + |
|
| 1220 | + // All we have to do to decline, is to move the item to the waste basket |
|
| 1221 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 1222 | + $sourcefolder = $this->openParentFolder(); |
|
| 1223 | + |
|
| 1224 | + $messageprops = mapi_getprops($this->message, [PR_ENTRYID]); |
|
| 1225 | + |
|
| 1226 | + // Release the message |
|
| 1227 | + $this->message = null; |
|
| 1228 | + |
|
| 1229 | + // Move the message to the waste basket |
|
| 1230 | + mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1231 | + |
|
| 1232 | + $result = true; |
|
| 1233 | + } |
|
| 1234 | + |
|
| 1235 | + return $result; |
|
| 1236 | + } |
|
| 1237 | + |
|
| 1238 | + /** |
|
| 1239 | + * Removes a meeting request from the calendar when the user presses the |
|
| 1240 | + * 'remove from calendar' button in response to a meeting cancellation. |
|
| 1241 | + * |
|
| 1242 | + * @param string $basedate if specified contains starttime of day of an occurrence |
|
| 1243 | + */ |
|
| 1244 | + public function doRemoveFromCalendar($basedate) { |
|
| 1245 | + if ($this->isLocalOrganiser()) { |
|
| 1246 | + return false; |
|
| 1247 | + } |
|
| 1248 | + |
|
| 1249 | + $messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_ENTRYID, PR_MESSAGE_CLASS]); |
|
| 1250 | + |
|
| 1251 | + $goid = $messageprops[$this->proptags['goid']]; |
|
| 1252 | + |
|
| 1253 | + if (isset($messageprops[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 1254 | + $delegatorStore = $this->getDelegatorStore($messageprops[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 1255 | + |
|
| 1256 | + $store = $delegatorStore['store']; |
|
| 1257 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 1258 | + } |
|
| 1259 | + else { |
|
| 1260 | + $store = $this->store; |
|
| 1261 | + $calFolder = $this->openDefaultCalendar(); |
|
| 1262 | + } |
|
| 1263 | + |
|
| 1264 | + // check for calendar access before deleting the calendar item |
|
| 1265 | + if ($this->checkCalendarWriteAccess($store) !== true) { |
|
| 1266 | + // Throw an exception that we don't have write permissions on calendar folder, |
|
| 1267 | + // allow caller to fill the error message |
|
| 1268 | + throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 1269 | + } |
|
| 1270 | + |
|
| 1271 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 1272 | + // get the source folder of the meeting message |
|
| 1273 | + $sourcefolder = $this->openParentFolder(); |
|
| 1274 | + |
|
| 1275 | + // Check if the message is a meeting request in the inbox or a calendaritem by checking the message class |
|
| 1276 | + if ($this->isMeetingCancellation($messageprops[PR_MESSAGE_CLASS])) { |
|
| 1277 | + // get the basedate to check for exception |
|
| 1278 | + $basedate = $this->getBasedateFromGlobalID($goid); |
|
| 1279 | + |
|
| 1280 | + $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 1281 | + |
|
| 1282 | + if ($calendarItem !== false) { |
|
| 1283 | + // basedate is provided so open exception |
|
| 1284 | + if ($basedate) { |
|
| 1285 | + $exception = $this->getExceptionItem($calendarItem, $basedate); |
|
| 1286 | + |
|
| 1287 | + if ($exception !== false) { |
|
| 1288 | + // exception found, remove it from calendar |
|
| 1289 | + $this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store); |
|
| 1290 | + } |
|
| 1291 | + } |
|
| 1292 | + else { |
|
| 1293 | + // remove normal / recurring series from calendar |
|
| 1294 | + $entryids = mapi_getprops($calendarItem, [PR_ENTRYID]); |
|
| 1295 | + |
|
| 1296 | + $entryids = [$entryids[PR_ENTRYID]]; |
|
| 1297 | + |
|
| 1298 | + mapi_folder_copymessages($calFolder, $entryids, $wastebasket, MESSAGE_MOVE); |
|
| 1299 | + } |
|
| 1300 | + } |
|
| 1301 | + |
|
| 1302 | + // Release the message, because we are going to move it to wastebasket |
|
| 1303 | + $this->message = null; |
|
| 1304 | + |
|
| 1305 | + // Move the cancellation mail to wastebasket |
|
| 1306 | + mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1307 | + } |
|
| 1308 | + else { |
|
| 1309 | + // Here only properties are set on calendaritem, because user is responding from calendar. |
|
| 1310 | + if ($basedate) { |
|
| 1311 | + // remove the occurrence |
|
| 1312 | + $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store); |
|
| 1313 | + } |
|
| 1314 | + else { |
|
| 1315 | + // remove normal/recurring meeting item. |
|
| 1316 | + // Move the message to the waste basket |
|
| 1317 | + mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1318 | + } |
|
| 1319 | + } |
|
| 1320 | + } |
|
| 1321 | + |
|
| 1322 | + /** |
|
| 1323 | + * Function can be used to cancel any existing meeting and send cancellation mails to attendees. |
|
| 1324 | + * Should only be called from meeting object from calendar. |
|
| 1325 | + * |
|
| 1326 | + * @param string $basedate (optional) basedate of occurrence which should be cancelled |
|
| 1327 | + * @FIXME cancellation mail is also sent to attendee which has declined the meeting |
|
| 1328 | + * @FIXME don't send canellation mail when cancelling meeting from past |
|
| 1329 | + */ |
|
| 1330 | + public function doCancelInvitation($basedate = false) { |
|
| 1331 | + if (!$this->isLocalOrganiser()) { |
|
| 1332 | + return; |
|
| 1333 | + } |
|
| 1334 | + |
|
| 1335 | + // check write access for delegate |
|
| 1336 | + if ($this->checkCalendarWriteAccess($this->store) !== true) { |
|
| 1337 | + // Throw an exception that we don't have write permissions on calendar folder, |
|
| 1338 | + // error message will be filled by module |
|
| 1339 | + throw new MAPIException(null, MAPI_E_NO_ACCESS); |
|
| 1340 | + } |
|
| 1341 | + |
|
| 1342 | + $messageProps = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['recurring']]); |
|
| 1343 | + |
|
| 1344 | + if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) { |
|
| 1345 | + // cancellation of recurring series or one occurrence |
|
| 1346 | + $recurrence = new Recurrence($this->store, $this->message); |
|
| 1347 | + |
|
| 1348 | + // if basedate is specified then we are cancelling only one occurrence, so create exception for that occurrence |
|
| 1349 | + if ($basedate) { |
|
| 1350 | + $recurrence->createException([], $basedate, true); |
|
| 1351 | + } |
|
| 1352 | + |
|
| 1353 | + // update the meeting request |
|
| 1354 | + $this->updateMeetingRequest(); |
|
| 1355 | + |
|
| 1356 | + // send cancellation mails |
|
| 1357 | + $this->sendMeetingRequest(true, _('Canceled: '), $basedate); |
|
| 1358 | + |
|
| 1359 | + // save changes in the message |
|
| 1360 | + mapi_savechanges($this->message); |
|
| 1361 | + } |
|
| 1362 | + else { |
|
| 1363 | + // cancellation of normal meeting request |
|
| 1364 | + // Send the cancellation |
|
| 1365 | + $this->updateMeetingRequest(); |
|
| 1366 | + $this->sendMeetingRequest(true, _('Canceled: ')); |
|
| 1367 | + |
|
| 1368 | + // save changes in the message |
|
| 1369 | + mapi_savechanges($this->message); |
|
| 1370 | + } |
|
| 1371 | + |
|
| 1372 | + // if basedate is specified then we have already created exception of it so nothing should be done now |
|
| 1373 | + // but when cancelling normal / recurring meeting request we need to remove meeting from calendar |
|
| 1374 | + if ($basedate === false) { |
|
| 1375 | + // get the wastebasket folder, for delegate this will give wastebasket of delegate |
|
| 1376 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 1377 | + |
|
| 1378 | + // get the source folder of the meeting message |
|
| 1379 | + $sourcefolder = $this->openParentFolder(); |
|
| 1380 | + |
|
| 1381 | + // Move the message to the deleted items |
|
| 1382 | + mapi_folder_copymessages($sourcefolder, [$messageProps[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 1383 | + } |
|
| 1384 | + } |
|
| 1385 | + |
|
| 1386 | + /** |
|
| 1387 | + * Convert epoch to MAPI FileTime, number of 100-nanosecond units since |
|
| 1388 | + * the start of January 1, 1601. |
|
| 1389 | + * https://msdn.microsoft.com/en-us/library/office/cc765906.aspx. |
|
| 1390 | + * |
|
| 1391 | + * @param int the current epoch |
|
| 1392 | + * @param mixed $epoch |
|
| 1393 | + * |
|
| 1394 | + * @return the MAPI FileTime equalevent to the given epoch time |
|
| 1395 | + */ |
|
| 1396 | + public function epochToMapiFileTime($epoch) { |
|
| 1397 | + $nanoseconds_between_epoch = 116444736000000000; |
|
| 1398 | + |
|
| 1399 | + return ($epoch * 10000000) + $nanoseconds_between_epoch; |
|
| 1400 | + } |
|
| 1401 | + |
|
| 1402 | + /** |
|
| 1403 | + * Sets the properties in the message so that is can be sent |
|
| 1404 | + * as a meeting request. The caller has to submit the message. This |
|
| 1405 | + * is only used for new MeetingRequests. Pass the appointment item as $message |
|
| 1406 | + * in the constructor to do this. |
|
| 1407 | + * |
|
| 1408 | + * @param mixed $basedate |
|
| 1409 | + */ |
|
| 1410 | + public function setMeetingRequest($basedate = false) { |
|
| 1411 | + $props = mapi_getprops($this->message, [$this->proptags['updatecounter']]); |
|
| 1412 | + |
|
| 1413 | + // Create a new global id for this item |
|
| 1414 | + // https://msdn.microsoft.com/en-us/library/ee160198(v=exchg.80).aspx |
|
| 1415 | + $goid = pack('H*', '040000008200E00074C5B7101A82E00800000000'); |
|
| 1416 | + // Creation Time |
|
| 1417 | + $time = $this->epochToMapiFileTime(time()); |
|
| 1418 | + $highdatetime = $time >> 32; |
|
| 1419 | + $lowdatetime = $time & 0xFFFFFFFF; |
|
| 1420 | + $goid .= pack('II', $lowdatetime, $highdatetime); |
|
| 1421 | + // 8 Zeros |
|
| 1422 | + $goid .= pack('P', 0); |
|
| 1423 | + // Length of the random data |
|
| 1424 | + $goid .= pack('V', 16); |
|
| 1425 | + // Random data. |
|
| 1426 | + for ($i = 0; $i < 16; ++$i) { |
|
| 1427 | + $goid .= chr(rand(0, 255)); |
|
| 1428 | + } |
|
| 1429 | + |
|
| 1430 | + // Create a new appointment id for this item |
|
| 1431 | + $apptid = rand(); |
|
| 1432 | + |
|
| 1433 | + $props[PR_OWNER_APPT_ID] = $apptid; |
|
| 1434 | + $props[PR_ICON_INDEX] = 1026; |
|
| 1435 | + $props[$this->proptags['goid']] = $goid; |
|
| 1436 | + $props[$this->proptags['goid2']] = $goid; |
|
| 1437 | + |
|
| 1438 | + if (!isset($props[$this->proptags['updatecounter']])) { |
|
| 1439 | + $props[$this->proptags['updatecounter']] = 0; // OL also starts sequence no with zero. |
|
| 1440 | + $props[$this->proptags['last_updatecounter']] = 0; |
|
| 1441 | + } |
|
| 1442 | + |
|
| 1443 | + mapi_setprops($this->message, $props); |
|
| 1444 | + } |
|
| 1445 | + |
|
| 1446 | + /** |
|
| 1447 | + * Sends a meeting request by copying it to the outbox, converting |
|
| 1448 | + * the message class, adding some properties that are required only |
|
| 1449 | + * for sending the message and submitting the message. Set cancel to |
|
| 1450 | + * true if you wish to completely cancel the meeting request. You can |
|
| 1451 | + * specify an optional 'prefix' to prefix the sent message, which is normally |
|
| 1452 | + * 'Canceled: '. |
|
| 1453 | + * |
|
| 1454 | + * @param mixed $cancel |
|
| 1455 | + * @param mixed $prefix |
|
| 1456 | + * @param mixed $basedate |
|
| 1457 | + * @param mixed $modifiedRecips |
|
| 1458 | + * @param mixed $deletedRecips |
|
| 1459 | + */ |
|
| 1460 | + public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $modifiedRecips = false, $deletedRecips = false) { |
|
| 1461 | + $this->includesResources = false; |
|
| 1462 | + $this->nonAcceptingResources = []; |
|
| 1463 | + |
|
| 1464 | + // Get the properties of the message |
|
| 1465 | + $messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]); |
|
| 1466 | + |
|
| 1467 | + /* |
|
| 1468 | 1468 | * Submit message to non-resource recipients |
| 1469 | 1469 | */ |
| 1470 | - // Set BusyStatus to olTentative (1) |
|
| 1471 | - // Set MeetingStatus to olMeetingReceived |
|
| 1472 | - // Set ResponseStatus to olResponseNotResponded |
|
| 1470 | + // Set BusyStatus to olTentative (1) |
|
| 1471 | + // Set MeetingStatus to olMeetingReceived |
|
| 1472 | + // Set ResponseStatus to olResponseNotResponded |
|
| 1473 | 1473 | |
| 1474 | - /* |
|
| 1474 | + /* |
|
| 1475 | 1475 | * While sending recurrence meeting exceptions are not send as attachments |
| 1476 | 1476 | * because first all exceptions are send and then recurrence meeting is sent. |
| 1477 | 1477 | */ |
| 1478 | - if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) { |
|
| 1479 | - // Book resource |
|
| 1480 | - $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix); |
|
| 1481 | - |
|
| 1482 | - if (!$this->errorSetResource) { |
|
| 1483 | - $recurr = new Recurrence($this->openDefaultStore(), $this->message); |
|
| 1484 | - |
|
| 1485 | - // First send meetingrequest for recurring item |
|
| 1486 | - $this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips); |
|
| 1487 | - |
|
| 1488 | - // Then send all meeting request for all exceptions |
|
| 1489 | - $exceptions = $recurr->getAllExceptions(); |
|
| 1490 | - if ($exceptions) { |
|
| 1491 | - foreach ($exceptions as $exceptionBasedate) { |
|
| 1492 | - $attach = $recurr->getExceptionAttachment($exceptionBasedate); |
|
| 1493 | - |
|
| 1494 | - if ($attach) { |
|
| 1495 | - $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 1496 | - $this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips); |
|
| 1497 | - mapi_savechanges($attach); |
|
| 1498 | - } |
|
| 1499 | - } |
|
| 1500 | - } |
|
| 1501 | - } |
|
| 1502 | - } |
|
| 1503 | - else { |
|
| 1504 | - // Basedate found, an exception is to be send |
|
| 1505 | - if ($basedate) { |
|
| 1506 | - $recurr = new Recurrence($this->openDefaultStore(), $this->message); |
|
| 1507 | - |
|
| 1508 | - if ($cancel) { |
|
| 1509 | - // @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series |
|
| 1510 | - $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false); |
|
| 1511 | - } |
|
| 1512 | - else { |
|
| 1513 | - $attach = $recurr->getExceptionAttachment($basedate); |
|
| 1514 | - |
|
| 1515 | - if ($attach) { |
|
| 1516 | - $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 1517 | - |
|
| 1518 | - // Book resource for this occurrence |
|
| 1519 | - $resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate); |
|
| 1520 | - |
|
| 1521 | - if (!$this->errorSetResource) { |
|
| 1522 | - // Save all previous changes |
|
| 1523 | - mapi_savechanges($this->message); |
|
| 1524 | - |
|
| 1525 | - $this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips); |
|
| 1526 | - mapi_savechanges($occurrenceItem); |
|
| 1527 | - mapi_savechanges($attach); |
|
| 1528 | - } |
|
| 1529 | - } |
|
| 1530 | - } |
|
| 1531 | - } |
|
| 1532 | - else { |
|
| 1533 | - // This is normal meeting |
|
| 1534 | - $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix); |
|
| 1535 | - |
|
| 1536 | - if (!$this->errorSetResource) { |
|
| 1537 | - $this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips); |
|
| 1538 | - } |
|
| 1539 | - } |
|
| 1540 | - } |
|
| 1541 | - |
|
| 1542 | - if (isset($this->errorSetResource) && $this->errorSetResource) { |
|
| 1543 | - return [ |
|
| 1544 | - 'error' => $this->errorSetResource, |
|
| 1545 | - 'displayname' => $this->recipientDisplayname, |
|
| 1546 | - ]; |
|
| 1547 | - } |
|
| 1548 | - |
|
| 1549 | - return true; |
|
| 1550 | - } |
|
| 1551 | - |
|
| 1552 | - /** |
|
| 1553 | - * This function will get freebusy data for user based on the timeframe passed in arguments. |
|
| 1554 | - * |
|
| 1555 | - * @param {HexString} $entryID Entryid of the user for which we need to get freebusy data |
|
| 1556 | - * @param {Number} $start start offset for freebusy publish range |
|
| 1557 | - * @param {Number} $end end offset for freebusy publish range |
|
| 1558 | - * |
|
| 1559 | - * @return {Array} freebusy blocks for passed publish range |
|
| 1560 | - */ |
|
| 1561 | - public function getFreeBusyInfo($entryID, $start, $end) { |
|
| 1562 | - $result = []; |
|
| 1563 | - $fbsupport = mapi_freebusysupport_open($this->session); |
|
| 1564 | - |
|
| 1565 | - $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$entryID]); |
|
| 1566 | - |
|
| 1567 | - if ($fbDataArray[0] != null) { |
|
| 1568 | - foreach ($fbDataArray as $fbDataUser) { |
|
| 1569 | - $rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser); |
|
| 1570 | - if ($rangeuser1 == null) { |
|
| 1571 | - return $result; |
|
| 1572 | - } |
|
| 1573 | - |
|
| 1574 | - $enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end); |
|
| 1575 | - mapi_freebusyenumblock_reset($enumblock); |
|
| 1576 | - |
|
| 1577 | - while (true) { |
|
| 1578 | - $blocks = mapi_freebusyenumblock_next($enumblock, 100); |
|
| 1579 | - if (!$blocks) { |
|
| 1580 | - break; |
|
| 1581 | - } |
|
| 1582 | - |
|
| 1583 | - foreach ($blocks as $blockItem) { |
|
| 1584 | - $result[] = $blockItem; |
|
| 1585 | - } |
|
| 1586 | - } |
|
| 1587 | - } |
|
| 1588 | - } |
|
| 1589 | - |
|
| 1590 | - mapi_freebusysupport_close($fbsupport); |
|
| 1591 | - |
|
| 1592 | - return $result; |
|
| 1593 | - } |
|
| 1594 | - |
|
| 1595 | - /** |
|
| 1596 | - * Updates the message after an update has been performed (for example, |
|
| 1597 | - * changing the time of the meeting). This must be called before re-sending |
|
| 1598 | - * the meeting request. You can also call this function instead of 'setMeetingRequest()' |
|
| 1599 | - * as it will automatically call setMeetingRequest on this object if it is the first |
|
| 1600 | - * call to this function. |
|
| 1601 | - * |
|
| 1602 | - * @param mixed $basedate |
|
| 1603 | - */ |
|
| 1604 | - public function updateMeetingRequest($basedate = false) { |
|
| 1605 | - $messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]); |
|
| 1606 | - |
|
| 1607 | - if (!isset($messageprops[$this->proptags['goid']])) { |
|
| 1608 | - $this->setMeetingRequest($basedate); |
|
| 1609 | - } |
|
| 1610 | - else { |
|
| 1611 | - $counter = $messageprops[$this->proptags['last_updatecounter']] + 1; |
|
| 1612 | - |
|
| 1613 | - // increment value of last_updatecounter, last_updatecounter will be common for recurring series |
|
| 1614 | - // so even if you sending an exception only you need to update the last_updatecounter in the recurring series message |
|
| 1615 | - // this way we can make sure that every time we will be using a uniwue number for every operation |
|
| 1616 | - mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]); |
|
| 1617 | - } |
|
| 1618 | - } |
|
| 1619 | - |
|
| 1620 | - /** |
|
| 1621 | - * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object. |
|
| 1622 | - */ |
|
| 1623 | - public function isLocalOrganiser() { |
|
| 1624 | - $props = mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]); |
|
| 1625 | - |
|
| 1626 | - if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) { |
|
| 1627 | - // we are checking with calendar item |
|
| 1628 | - $calendarItem = $this->message; |
|
| 1629 | - } |
|
| 1630 | - else { |
|
| 1631 | - // we are checking with meeting request / response / cancellation mail |
|
| 1632 | - // get calendar items |
|
| 1633 | - $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 1634 | - } |
|
| 1635 | - |
|
| 1636 | - // even if we have received request/response for exception/occurrence then also |
|
| 1637 | - // we can check recurring series for organizer, no need to check with exception/occurrence |
|
| 1638 | - |
|
| 1639 | - if ($calendarItem !== false) { |
|
| 1640 | - $messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]); |
|
| 1641 | - |
|
| 1642 | - if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) { |
|
| 1643 | - return true; |
|
| 1644 | - } |
|
| 1645 | - } |
|
| 1646 | - |
|
| 1647 | - return false; |
|
| 1648 | - } |
|
| 1649 | - |
|
| 1650 | - /* |
|
| 1478 | + if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) { |
|
| 1479 | + // Book resource |
|
| 1480 | + $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix); |
|
| 1481 | + |
|
| 1482 | + if (!$this->errorSetResource) { |
|
| 1483 | + $recurr = new Recurrence($this->openDefaultStore(), $this->message); |
|
| 1484 | + |
|
| 1485 | + // First send meetingrequest for recurring item |
|
| 1486 | + $this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $modifiedRecips, $deletedRecips); |
|
| 1487 | + |
|
| 1488 | + // Then send all meeting request for all exceptions |
|
| 1489 | + $exceptions = $recurr->getAllExceptions(); |
|
| 1490 | + if ($exceptions) { |
|
| 1491 | + foreach ($exceptions as $exceptionBasedate) { |
|
| 1492 | + $attach = $recurr->getExceptionAttachment($exceptionBasedate); |
|
| 1493 | + |
|
| 1494 | + if ($attach) { |
|
| 1495 | + $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 1496 | + $this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $modifiedRecips, $deletedRecips); |
|
| 1497 | + mapi_savechanges($attach); |
|
| 1498 | + } |
|
| 1499 | + } |
|
| 1500 | + } |
|
| 1501 | + } |
|
| 1502 | + } |
|
| 1503 | + else { |
|
| 1504 | + // Basedate found, an exception is to be send |
|
| 1505 | + if ($basedate) { |
|
| 1506 | + $recurr = new Recurrence($this->openDefaultStore(), $this->message); |
|
| 1507 | + |
|
| 1508 | + if ($cancel) { |
|
| 1509 | + // @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series |
|
| 1510 | + $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false); |
|
| 1511 | + } |
|
| 1512 | + else { |
|
| 1513 | + $attach = $recurr->getExceptionAttachment($basedate); |
|
| 1514 | + |
|
| 1515 | + if ($attach) { |
|
| 1516 | + $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 1517 | + |
|
| 1518 | + // Book resource for this occurrence |
|
| 1519 | + $resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate); |
|
| 1520 | + |
|
| 1521 | + if (!$this->errorSetResource) { |
|
| 1522 | + // Save all previous changes |
|
| 1523 | + mapi_savechanges($this->message); |
|
| 1524 | + |
|
| 1525 | + $this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $modifiedRecips, $deletedRecips); |
|
| 1526 | + mapi_savechanges($occurrenceItem); |
|
| 1527 | + mapi_savechanges($attach); |
|
| 1528 | + } |
|
| 1529 | + } |
|
| 1530 | + } |
|
| 1531 | + } |
|
| 1532 | + else { |
|
| 1533 | + // This is normal meeting |
|
| 1534 | + $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix); |
|
| 1535 | + |
|
| 1536 | + if (!$this->errorSetResource) { |
|
| 1537 | + $this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $modifiedRecips, $deletedRecips); |
|
| 1538 | + } |
|
| 1539 | + } |
|
| 1540 | + } |
|
| 1541 | + |
|
| 1542 | + if (isset($this->errorSetResource) && $this->errorSetResource) { |
|
| 1543 | + return [ |
|
| 1544 | + 'error' => $this->errorSetResource, |
|
| 1545 | + 'displayname' => $this->recipientDisplayname, |
|
| 1546 | + ]; |
|
| 1547 | + } |
|
| 1548 | + |
|
| 1549 | + return true; |
|
| 1550 | + } |
|
| 1551 | + |
|
| 1552 | + /** |
|
| 1553 | + * This function will get freebusy data for user based on the timeframe passed in arguments. |
|
| 1554 | + * |
|
| 1555 | + * @param {HexString} $entryID Entryid of the user for which we need to get freebusy data |
|
| 1556 | + * @param {Number} $start start offset for freebusy publish range |
|
| 1557 | + * @param {Number} $end end offset for freebusy publish range |
|
| 1558 | + * |
|
| 1559 | + * @return {Array} freebusy blocks for passed publish range |
|
| 1560 | + */ |
|
| 1561 | + public function getFreeBusyInfo($entryID, $start, $end) { |
|
| 1562 | + $result = []; |
|
| 1563 | + $fbsupport = mapi_freebusysupport_open($this->session); |
|
| 1564 | + |
|
| 1565 | + $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$entryID]); |
|
| 1566 | + |
|
| 1567 | + if ($fbDataArray[0] != null) { |
|
| 1568 | + foreach ($fbDataArray as $fbDataUser) { |
|
| 1569 | + $rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser); |
|
| 1570 | + if ($rangeuser1 == null) { |
|
| 1571 | + return $result; |
|
| 1572 | + } |
|
| 1573 | + |
|
| 1574 | + $enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end); |
|
| 1575 | + mapi_freebusyenumblock_reset($enumblock); |
|
| 1576 | + |
|
| 1577 | + while (true) { |
|
| 1578 | + $blocks = mapi_freebusyenumblock_next($enumblock, 100); |
|
| 1579 | + if (!$blocks) { |
|
| 1580 | + break; |
|
| 1581 | + } |
|
| 1582 | + |
|
| 1583 | + foreach ($blocks as $blockItem) { |
|
| 1584 | + $result[] = $blockItem; |
|
| 1585 | + } |
|
| 1586 | + } |
|
| 1587 | + } |
|
| 1588 | + } |
|
| 1589 | + |
|
| 1590 | + mapi_freebusysupport_close($fbsupport); |
|
| 1591 | + |
|
| 1592 | + return $result; |
|
| 1593 | + } |
|
| 1594 | + |
|
| 1595 | + /** |
|
| 1596 | + * Updates the message after an update has been performed (for example, |
|
| 1597 | + * changing the time of the meeting). This must be called before re-sending |
|
| 1598 | + * the meeting request. You can also call this function instead of 'setMeetingRequest()' |
|
| 1599 | + * as it will automatically call setMeetingRequest on this object if it is the first |
|
| 1600 | + * call to this function. |
|
| 1601 | + * |
|
| 1602 | + * @param mixed $basedate |
|
| 1603 | + */ |
|
| 1604 | + public function updateMeetingRequest($basedate = false) { |
|
| 1605 | + $messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]); |
|
| 1606 | + |
|
| 1607 | + if (!isset($messageprops[$this->proptags['goid']])) { |
|
| 1608 | + $this->setMeetingRequest($basedate); |
|
| 1609 | + } |
|
| 1610 | + else { |
|
| 1611 | + $counter = $messageprops[$this->proptags['last_updatecounter']] + 1; |
|
| 1612 | + |
|
| 1613 | + // increment value of last_updatecounter, last_updatecounter will be common for recurring series |
|
| 1614 | + // so even if you sending an exception only you need to update the last_updatecounter in the recurring series message |
|
| 1615 | + // this way we can make sure that every time we will be using a uniwue number for every operation |
|
| 1616 | + mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]); |
|
| 1617 | + } |
|
| 1618 | + } |
|
| 1619 | + |
|
| 1620 | + /** |
|
| 1621 | + * Returns TRUE if we are the organiser of the meeting. Can be used with any type of meeting object. |
|
| 1622 | + */ |
|
| 1623 | + public function isLocalOrganiser() { |
|
| 1624 | + $props = mapi_getprops($this->message, [$this->proptags['goid'], PR_MESSAGE_CLASS]); |
|
| 1625 | + |
|
| 1626 | + if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) { |
|
| 1627 | + // we are checking with calendar item |
|
| 1628 | + $calendarItem = $this->message; |
|
| 1629 | + } |
|
| 1630 | + else { |
|
| 1631 | + // we are checking with meeting request / response / cancellation mail |
|
| 1632 | + // get calendar items |
|
| 1633 | + $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 1634 | + } |
|
| 1635 | + |
|
| 1636 | + // even if we have received request/response for exception/occurrence then also |
|
| 1637 | + // we can check recurring series for organizer, no need to check with exception/occurrence |
|
| 1638 | + |
|
| 1639 | + if ($calendarItem !== false) { |
|
| 1640 | + $messageProps = mapi_getprops($calendarItem, [$this->proptags['responsestatus']]); |
|
| 1641 | + |
|
| 1642 | + if (isset($messageProps[$this->proptags['responsestatus']]) && $messageProps[$this->proptags['responsestatus']] === olResponseOrganized) { |
|
| 1643 | + return true; |
|
| 1644 | + } |
|
| 1645 | + } |
|
| 1646 | + |
|
| 1647 | + return false; |
|
| 1648 | + } |
|
| 1649 | + |
|
| 1650 | + /* |
|
| 1651 | 1651 | * Support functions - INTERNAL ONLY |
| 1652 | 1652 | *************************************************************************************************** |
| 1653 | 1653 | */ |
| 1654 | 1654 | |
| 1655 | - /** |
|
| 1656 | - * Return the tracking status of a recipient based on the IPM class (passed). |
|
| 1657 | - * |
|
| 1658 | - * @param mixed $class |
|
| 1659 | - */ |
|
| 1660 | - public function getTrackStatus($class) { |
|
| 1661 | - $status = olRecipientTrackStatusNone; |
|
| 1662 | - |
|
| 1663 | - switch ($class) { |
|
| 1664 | - case 'IPM.Schedule.Meeting.Resp.Pos': |
|
| 1665 | - $status = olRecipientTrackStatusAccepted; |
|
| 1666 | - |
|
| 1667 | - break; |
|
| 1668 | - |
|
| 1669 | - case 'IPM.Schedule.Meeting.Resp.Tent': |
|
| 1670 | - $status = olRecipientTrackStatusTentative; |
|
| 1671 | - |
|
| 1672 | - break; |
|
| 1673 | - |
|
| 1674 | - case 'IPM.Schedule.Meeting.Resp.Neg': |
|
| 1675 | - $status = olRecipientTrackStatusDeclined; |
|
| 1676 | - |
|
| 1677 | - break; |
|
| 1678 | - } |
|
| 1679 | - |
|
| 1680 | - return $status; |
|
| 1681 | - } |
|
| 1682 | - |
|
| 1683 | - /** |
|
| 1684 | - * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request |
|
| 1685 | - * object. |
|
| 1686 | - */ |
|
| 1687 | - public function openParentFolder() { |
|
| 1688 | - $messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]); |
|
| 1689 | - |
|
| 1690 | - return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]); |
|
| 1691 | - } |
|
| 1692 | - |
|
| 1693 | - /** |
|
| 1694 | - * Function will return resource of the default calendar folder of store. |
|
| 1695 | - * |
|
| 1696 | - * @param MAPIStore $store {optional} user store whose default calendar should be opened |
|
| 1697 | - * |
|
| 1698 | - * @return MAPIFolder default calendar folder of store |
|
| 1699 | - */ |
|
| 1700 | - public function openDefaultCalendar($store = false) { |
|
| 1701 | - return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store); |
|
| 1702 | - } |
|
| 1703 | - |
|
| 1704 | - /** |
|
| 1705 | - * Function will return resource of the default outbox folder of store. |
|
| 1706 | - * |
|
| 1707 | - * @param MAPIStore $store {optional} user store whose default outbox should be opened |
|
| 1708 | - * |
|
| 1709 | - * @return MAPIFolder default outbox folder of store |
|
| 1710 | - */ |
|
| 1711 | - public function openDefaultOutbox($store = false) { |
|
| 1712 | - return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store); |
|
| 1713 | - } |
|
| 1714 | - |
|
| 1715 | - /** |
|
| 1716 | - * Function will return resource of the default wastebasket folder of store. |
|
| 1717 | - * |
|
| 1718 | - * @param MAPIStore $store {optional} user store whose default wastebasket should be opened |
|
| 1719 | - * |
|
| 1720 | - * @return MAPIFolder default wastebasket folder of store |
|
| 1721 | - */ |
|
| 1722 | - public function openDefaultWastebasket($store = false) { |
|
| 1723 | - return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store); |
|
| 1724 | - } |
|
| 1725 | - |
|
| 1726 | - /** |
|
| 1727 | - * Function will return resource of the default calendar folder of store. |
|
| 1728 | - * |
|
| 1729 | - * @param MAPIStore $store {optional} user store whose default calendar should be opened |
|
| 1730 | - * |
|
| 1731 | - * @return MAPIFolder default calendar folder of store |
|
| 1732 | - */ |
|
| 1733 | - public function getDefaultWastebasketEntryID($store = false) { |
|
| 1734 | - return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store); |
|
| 1735 | - } |
|
| 1736 | - |
|
| 1737 | - /** |
|
| 1738 | - * Function will return resource of the default sent mail folder of store. |
|
| 1739 | - * |
|
| 1740 | - * @param MAPIStore $store {optional} user store whose default sent mail should be opened |
|
| 1741 | - * |
|
| 1742 | - * @return MAPIFolder default sent mail folder of store |
|
| 1743 | - */ |
|
| 1744 | - public function getDefaultSentmailEntryID($store = false) { |
|
| 1745 | - return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store); |
|
| 1746 | - } |
|
| 1747 | - |
|
| 1748 | - /** |
|
| 1749 | - * Function will return entryid of any default folder of store. This method is useful when you want |
|
| 1750 | - * to get entryid of folder which is stored as properties of inbox folder |
|
| 1751 | - * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID). |
|
| 1752 | - * |
|
| 1753 | - * @param PropTag $prop proptag of the folder for which we want to get entryid |
|
| 1754 | - * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder |
|
| 1755 | - * |
|
| 1756 | - * @return BinString entryid of folder pointed by $prop |
|
| 1757 | - */ |
|
| 1758 | - public function getDefaultFolderEntryID($prop, $store = false) { |
|
| 1759 | - try { |
|
| 1760 | - $inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store); |
|
| 1761 | - $inboxprops = mapi_getprops($inbox, [$prop]); |
|
| 1762 | - if (isset($inboxprops[$prop])) { |
|
| 1763 | - return $inboxprops[$prop]; |
|
| 1764 | - } |
|
| 1765 | - } |
|
| 1766 | - catch (MAPIException $e) { |
|
| 1767 | - // public store doesn't support this method |
|
| 1768 | - if ($e->getCode() == MAPI_E_NO_SUPPORT) { |
|
| 1769 | - // don't propagate this error to parent handlers, if store doesn't support it |
|
| 1770 | - $e->setHandled(); |
|
| 1771 | - } |
|
| 1772 | - } |
|
| 1773 | - |
|
| 1774 | - return false; |
|
| 1775 | - } |
|
| 1776 | - |
|
| 1777 | - /** |
|
| 1778 | - * Function will return resource of any default folder of store. |
|
| 1779 | - * |
|
| 1780 | - * @param PropTag $prop proptag of the folder that we want to open |
|
| 1781 | - * @param MAPIStore $store {optional} user store from which we need to open default folder |
|
| 1782 | - * |
|
| 1783 | - * @return MAPIFolder default folder of store |
|
| 1784 | - */ |
|
| 1785 | - public function openDefaultFolder($prop, $store = false) { |
|
| 1786 | - $folder = false; |
|
| 1787 | - $entryid = $this->getDefaultFolderEntryID($prop, $store); |
|
| 1788 | - |
|
| 1789 | - if ($entryid !== false) { |
|
| 1790 | - $folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid); |
|
| 1791 | - } |
|
| 1792 | - |
|
| 1793 | - return $folder; |
|
| 1794 | - } |
|
| 1795 | - |
|
| 1796 | - /** |
|
| 1797 | - * Function will return entryid of default folder from store. This method is useful when you want |
|
| 1798 | - * to get entryid of folder which is stored as store properties |
|
| 1799 | - * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID). |
|
| 1800 | - * |
|
| 1801 | - * @param PropTag $prop proptag of the folder whose entryid we want to get |
|
| 1802 | - * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder |
|
| 1803 | - * |
|
| 1804 | - * @return BinString entryid of default folder from store |
|
| 1805 | - */ |
|
| 1806 | - public function getBaseEntryID($prop, $store = false) { |
|
| 1807 | - $storeprops = mapi_getprops($store ? $store : $this->store, [$prop]); |
|
| 1808 | - if (!isset($storeprops[$prop])) { |
|
| 1809 | - return false; |
|
| 1810 | - } |
|
| 1811 | - |
|
| 1812 | - return $storeprops[$prop]; |
|
| 1813 | - } |
|
| 1814 | - |
|
| 1815 | - /** |
|
| 1816 | - * Function will return resource of any default folder of store. |
|
| 1817 | - * |
|
| 1818 | - * @param PropTag $prop proptag of the folder that we want to open |
|
| 1819 | - * @param MAPIStore $store {optional} user store from which we need to open default folder |
|
| 1820 | - * |
|
| 1821 | - * @return MAPIFolder default folder of store |
|
| 1822 | - */ |
|
| 1823 | - public function openBaseFolder($prop, $store = false) { |
|
| 1824 | - $folder = false; |
|
| 1825 | - $entryid = $this->getBaseEntryID($prop, $store); |
|
| 1826 | - |
|
| 1827 | - if ($entryid !== false) { |
|
| 1828 | - $folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid); |
|
| 1829 | - } |
|
| 1830 | - |
|
| 1831 | - return $folder; |
|
| 1832 | - } |
|
| 1833 | - |
|
| 1834 | - /** |
|
| 1835 | - * Function checks whether user has access over the specified folder or not. |
|
| 1836 | - * |
|
| 1837 | - * @param Binary $entryid entryid The entryid of the folder to check |
|
| 1838 | - * @param MAPIStore $store (optional) store from which folder should be opened |
|
| 1839 | - * |
|
| 1840 | - * @return bool true if user has an access over the folder, false if not |
|
| 1841 | - */ |
|
| 1842 | - public function checkFolderWriteAccess($entryid, $store = false) { |
|
| 1843 | - $accessToFolder = false; |
|
| 1844 | - |
|
| 1845 | - if (!empty($entryid)) { |
|
| 1846 | - if ($store === false) { |
|
| 1847 | - $store = $this->store; |
|
| 1848 | - } |
|
| 1849 | - |
|
| 1850 | - try { |
|
| 1851 | - $folder = mapi_msgstore_openentry($store, $entryid); |
|
| 1852 | - $folderProps = mapi_getprops($folder, [PR_ACCESS]); |
|
| 1853 | - if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) { |
|
| 1854 | - $accessToFolder = true; |
|
| 1855 | - } |
|
| 1856 | - } |
|
| 1857 | - catch (MAPIException $e) { |
|
| 1858 | - // we don't have rights to open folder, so return false |
|
| 1859 | - if ($e->getCode() == MAPI_E_NO_ACCESS) { |
|
| 1860 | - return $accessToFolder; |
|
| 1861 | - } |
|
| 1862 | - |
|
| 1863 | - // rethrow other errors |
|
| 1864 | - throw $e; |
|
| 1865 | - } |
|
| 1866 | - } |
|
| 1867 | - |
|
| 1868 | - return $accessToFolder; |
|
| 1869 | - } |
|
| 1870 | - |
|
| 1871 | - /** |
|
| 1872 | - * Function checks whether user has access over the specified folder or not. |
|
| 1873 | - * |
|
| 1874 | - * @param object MAPI Message Store Object |
|
| 1875 | - * @param mixed $store |
|
| 1876 | - * |
|
| 1877 | - * @return bool true if user has an access over the folder, false if not |
|
| 1878 | - */ |
|
| 1879 | - public function checkCalendarWriteAccess($store = false) { |
|
| 1880 | - if ($store === false) { |
|
| 1881 | - // If this meeting request is received by a delegate then open delegator's store. |
|
| 1882 | - $messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 1883 | - if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 1884 | - $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 1885 | - |
|
| 1886 | - $store = $delegatorStore['store']; |
|
| 1887 | - } |
|
| 1888 | - else { |
|
| 1889 | - $store = $this->store; |
|
| 1890 | - } |
|
| 1891 | - } |
|
| 1892 | - |
|
| 1893 | - // If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item |
|
| 1894 | - $provider = mapi_getprops($store, [PR_MDB_PROVIDER]); |
|
| 1895 | - if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) { |
|
| 1896 | - $entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]); |
|
| 1897 | - $entryid = $entryid[PR_PARENT_ENTRYID]; |
|
| 1898 | - } |
|
| 1899 | - else { |
|
| 1900 | - $entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store); |
|
| 1901 | - if ($entryid === false) { |
|
| 1902 | - $entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store); |
|
| 1903 | - } |
|
| 1904 | - |
|
| 1905 | - if ($entryid === false) { |
|
| 1906 | - return false; |
|
| 1907 | - } |
|
| 1908 | - } |
|
| 1909 | - |
|
| 1910 | - return $this->checkFolderWriteAccess($entryid, $store); |
|
| 1911 | - } |
|
| 1912 | - |
|
| 1913 | - /** |
|
| 1914 | - * Function will resolve the user and open its store. |
|
| 1915 | - * |
|
| 1916 | - * @param string $ownerentryid the entryid of the user |
|
| 1917 | - * |
|
| 1918 | - * @return MAPIStore store of the user |
|
| 1919 | - */ |
|
| 1920 | - public function openCustomUserStore($ownerentryid) { |
|
| 1921 | - $ab = mapi_openaddressbook($this->session); |
|
| 1922 | - |
|
| 1923 | - try { |
|
| 1924 | - $mailuser = mapi_ab_openentry($ab, $ownerentryid); |
|
| 1925 | - } |
|
| 1926 | - catch (MAPIException $e) { |
|
| 1927 | - return; |
|
| 1928 | - } |
|
| 1929 | - |
|
| 1930 | - $mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]); |
|
| 1931 | - $storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]); |
|
| 1932 | - |
|
| 1933 | - return mapi_openmsgstore($this->session, $storeid); |
|
| 1934 | - } |
|
| 1935 | - |
|
| 1936 | - /** |
|
| 1937 | - * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request. |
|
| 1938 | - * |
|
| 1939 | - * @param int $status response status of attendee |
|
| 1940 | - * @param array $proposeNewTimeProps properties of attendee's proposal |
|
| 1941 | - * @param int $basedate date of occurrence which attendee has responded |
|
| 1942 | - * @param mixed $body |
|
| 1943 | - * @param mixed $store |
|
| 1944 | - * @param mixed $calFolder |
|
| 1945 | - */ |
|
| 1946 | - public function createResponse($status, $proposeNewTimeProps = [], $body = false, $store, $basedate = false, $calFolder) { |
|
| 1947 | - $messageprops = mapi_getprops($this->message, [ |
|
| 1948 | - PR_SENT_REPRESENTING_ENTRYID, |
|
| 1949 | - PR_SENT_REPRESENTING_EMAIL_ADDRESS, |
|
| 1950 | - PR_SENT_REPRESENTING_ADDRTYPE, |
|
| 1951 | - PR_SENT_REPRESENTING_NAME, |
|
| 1952 | - PR_SENT_REPRESENTING_SEARCH_KEY, |
|
| 1953 | - $this->proptags['goid'], |
|
| 1954 | - $this->proptags['goid2'], |
|
| 1955 | - $this->proptags['location'], |
|
| 1956 | - $this->proptags['startdate'], |
|
| 1957 | - $this->proptags['duedate'], |
|
| 1958 | - $this->proptags['recurring'], |
|
| 1959 | - $this->proptags['recurring_pattern'], |
|
| 1960 | - $this->proptags['recurrence_data'], |
|
| 1961 | - $this->proptags['timezone_data'], |
|
| 1962 | - $this->proptags['timezone'], |
|
| 1963 | - $this->proptags['updatecounter'], |
|
| 1964 | - PR_SUBJECT, |
|
| 1965 | - PR_MESSAGE_CLASS, |
|
| 1966 | - PR_OWNER_APPT_ID, |
|
| 1967 | - $this->proptags['is_exception'], |
|
| 1968 | - ]); |
|
| 1969 | - |
|
| 1970 | - if ($basedate && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) { |
|
| 1971 | - // we are creating response from a recurring calendar item object |
|
| 1972 | - // We found basedate,so opened occurrence and get properties. |
|
| 1973 | - $recurr = new Recurrence($store, $this->message); |
|
| 1974 | - $exception = $recurr->getExceptionAttachment($basedate); |
|
| 1975 | - |
|
| 1976 | - if ($exception) { |
|
| 1977 | - // Exception found, Now retrieve properties |
|
| 1978 | - $imessage = mapi_attach_openobj($exception, 0); |
|
| 1979 | - $imsgprops = mapi_getprops($imessage); |
|
| 1980 | - |
|
| 1981 | - // If location is provided, copy it to the response |
|
| 1982 | - if (isset($imsgprops[$this->proptags['location']])) { |
|
| 1983 | - $messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']]; |
|
| 1984 | - } |
|
| 1985 | - |
|
| 1986 | - // Update $messageprops with timings of occurrence |
|
| 1987 | - $messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']]; |
|
| 1988 | - $messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']]; |
|
| 1989 | - |
|
| 1990 | - // Meeting related properties |
|
| 1991 | - $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']]; |
|
| 1992 | - $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']]; |
|
| 1993 | - $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT]; |
|
| 1994 | - } |
|
| 1995 | - else { |
|
| 1996 | - // Exceptions is deleted. |
|
| 1997 | - // Update $messageprops with timings of occurrence |
|
| 1998 | - $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
|
| 1999 | - $messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
|
| 2000 | - |
|
| 2001 | - $props[$this->proptags['meetingstatus']] = olNonMeeting; |
|
| 2002 | - $props[$this->proptags['responsestatus']] = olResponseNone; |
|
| 2003 | - } |
|
| 2004 | - |
|
| 2005 | - $props[$this->proptags['recurring']] = false; |
|
| 2006 | - $props[$this->proptags['is_exception']] = true; |
|
| 2007 | - } |
|
| 2008 | - else { |
|
| 2009 | - // we are creating a response from meeting request mail (it could be recurring or non-recurring) |
|
| 2010 | - // Send all recurrence info in response, if this is a recurrence meeting. |
|
| 2011 | - $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]; |
|
| 2012 | - $isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']]; |
|
| 2013 | - if ($isRecurring || $isException) { |
|
| 2014 | - if ($isRecurring) { |
|
| 2015 | - $props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']]; |
|
| 2016 | - } |
|
| 2017 | - if ($isException) { |
|
| 2018 | - $props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']]; |
|
| 2019 | - } |
|
| 2020 | - $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 2021 | - |
|
| 2022 | - $calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]); |
|
| 2023 | - $recurr = new Recurrence($store, $calendaritem); |
|
| 2024 | - } |
|
| 2025 | - } |
|
| 2026 | - |
|
| 2027 | - // we are sending a response for recurring meeting request (or exception), so set some required properties |
|
| 2028 | - if (isset($recurr) && $recurr) { |
|
| 2029 | - if (!empty($messageprops[$this->proptags['recurring_pattern']])) { |
|
| 2030 | - $props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']]; |
|
| 2031 | - } |
|
| 2032 | - |
|
| 2033 | - if (!empty($messageprops[$this->proptags['recurrence_data']])) { |
|
| 2034 | - $props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']]; |
|
| 2035 | - } |
|
| 2036 | - |
|
| 2037 | - $props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']]; |
|
| 2038 | - $props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']]; |
|
| 2039 | - |
|
| 2040 | - $this->generateRecurDates($recurr, $messageprops, $props); |
|
| 2041 | - } |
|
| 2042 | - |
|
| 2043 | - // Create a response message |
|
| 2044 | - $recip = []; |
|
| 2045 | - $recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 2046 | - $recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 2047 | - $recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 2048 | - $recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME]; |
|
| 2049 | - $recip[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 2050 | - $recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 2051 | - |
|
| 2052 | - switch ($status) { |
|
| 2053 | - case olResponseAccepted: |
|
| 2054 | - $classpostfix = 'Pos'; |
|
| 2055 | - $subjectprefix = _('Accepted'); |
|
| 2056 | - |
|
| 2057 | - break; |
|
| 2058 | - |
|
| 2059 | - case olResponseDeclined: |
|
| 2060 | - $classpostfix = 'Neg'; |
|
| 2061 | - $subjectprefix = _('Declined'); |
|
| 2062 | - |
|
| 2063 | - break; |
|
| 2064 | - |
|
| 2065 | - case olResponseTentative: |
|
| 2066 | - $classpostfix = 'Tent'; |
|
| 2067 | - $subjectprefix = _('Tentatively accepted'); |
|
| 2068 | - |
|
| 2069 | - break; |
|
| 2070 | - } |
|
| 2071 | - |
|
| 2072 | - if (!empty($proposeNewTimeProps)) { |
|
| 2073 | - // if attendee has proposed new time then change subject prefix |
|
| 2074 | - $subjectprefix = _('New Time Proposed'); |
|
| 2075 | - } |
|
| 2076 | - |
|
| 2077 | - $props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT]; |
|
| 2078 | - |
|
| 2079 | - $props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix; |
|
| 2080 | - if (isset($messageprops[PR_OWNER_APPT_ID])) { |
|
| 2081 | - $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID]; |
|
| 2082 | - } |
|
| 2083 | - |
|
| 2084 | - // Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3). |
|
| 2085 | - $props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate); |
|
| 2086 | - $props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']]; |
|
| 2087 | - $props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0; |
|
| 2088 | - |
|
| 2089 | - if (!empty($proposeNewTimeProps)) { |
|
| 2090 | - // merge proposal properties to message properties which will be sent to organizer |
|
| 2091 | - $props = $proposeNewTimeProps + $props; |
|
| 2092 | - } |
|
| 2093 | - |
|
| 2094 | - // Set body message in Appointment |
|
| 2095 | - if (isset($body)) { |
|
| 2096 | - $props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body; |
|
| 2097 | - } |
|
| 2098 | - |
|
| 2099 | - // PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message |
|
| 2100 | - $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']]; |
|
| 2101 | - $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']]; |
|
| 2102 | - |
|
| 2103 | - // Set startdate and duedate in response mail. |
|
| 2104 | - $props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']]; |
|
| 2105 | - $props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']]; |
|
| 2106 | - |
|
| 2107 | - // responselocation is used in the UI in Outlook on the response message |
|
| 2108 | - if (isset($messageprops[$this->proptags['location']])) { |
|
| 2109 | - $props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']]; |
|
| 2110 | - $props[$this->proptags['location']] = $messageprops[$this->proptags['location']]; |
|
| 2111 | - } |
|
| 2112 | - |
|
| 2113 | - $message = $this->createOutgoingMessage($store); |
|
| 2114 | - |
|
| 2115 | - mapi_setprops($message, $props); |
|
| 2116 | - mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]); |
|
| 2117 | - mapi_savechanges($message); |
|
| 2118 | - mapi_message_submitmessage($message); |
|
| 2119 | - } |
|
| 2120 | - |
|
| 2121 | - /** |
|
| 2122 | - * Function which finds items in calendar based on globalId and cleanGlobalId. |
|
| 2123 | - * |
|
| 2124 | - * @param binary $goid GlobalID(0x3) of item |
|
| 2125 | - * @param MAPIFolder $calendar MAPI_folder of user (optional) |
|
| 2126 | - * @param bool $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3) |
|
| 2127 | - */ |
|
| 2128 | - public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) { |
|
| 2129 | - if ($calendar === false) { |
|
| 2130 | - // Open the Calendar |
|
| 2131 | - $calendar = $this->openDefaultCalendar(); |
|
| 2132 | - } |
|
| 2133 | - |
|
| 2134 | - // Find the item by restricting all items to the correct ID |
|
| 2135 | - $restrict = [ |
|
| 2136 | - RES_AND, |
|
| 2137 | - [ |
|
| 2138 | - [ |
|
| 2139 | - RES_PROPERTY, |
|
| 2140 | - [ |
|
| 2141 | - RELOP => RELOP_EQ, |
|
| 2142 | - ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']), |
|
| 2143 | - VALUE => $goid, |
|
| 2144 | - ], |
|
| 2145 | - ], |
|
| 2146 | - ], ]; |
|
| 2147 | - |
|
| 2148 | - $calendarcontents = mapi_folder_getcontentstable($calendar); |
|
| 2149 | - |
|
| 2150 | - $rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict); |
|
| 2151 | - |
|
| 2152 | - if (empty($rows)) { |
|
| 2153 | - return; |
|
| 2154 | - } |
|
| 2155 | - |
|
| 2156 | - $calendaritems = []; |
|
| 2157 | - |
|
| 2158 | - // In principle, there should only be one row, but we'll handle them all just in case |
|
| 2159 | - foreach ($rows as $row) { |
|
| 2160 | - $calendaritems[] = $row[PR_ENTRYID]; |
|
| 2161 | - } |
|
| 2162 | - |
|
| 2163 | - return $calendaritems; |
|
| 2164 | - } |
|
| 2165 | - |
|
| 2166 | - // Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the |
|
| 2167 | - // same SMTP address when converted to SMTP |
|
| 2168 | - public function compareABEntryIDs($entryid1, $entryid2) { |
|
| 2169 | - // If the session was not passed, just do a 'normal' compare. |
|
| 2170 | - if (!$this->session) { |
|
| 2171 | - return $entryid1 == $entryid2; |
|
| 2172 | - } |
|
| 2173 | - |
|
| 2174 | - $smtp1 = $this->getSMTPAddress($entryid1); |
|
| 2175 | - $smtp2 = $this->getSMTPAddress($entryid2); |
|
| 2176 | - |
|
| 2177 | - if ($smtp1 == $smtp2) { |
|
| 2178 | - return true; |
|
| 2179 | - } |
|
| 2180 | - |
|
| 2181 | - return false; |
|
| 2182 | - } |
|
| 2183 | - |
|
| 2184 | - // Gets the SMTP address of the passed addressbook entryid |
|
| 2185 | - public function getSMTPAddress($entryid) { |
|
| 2186 | - if (!$this->session) { |
|
| 2187 | - return false; |
|
| 2188 | - } |
|
| 2189 | - |
|
| 2190 | - $ab = mapi_openaddressbook($this->session); |
|
| 2191 | - |
|
| 2192 | - $abitem = mapi_ab_openentry($ab, $entryid); |
|
| 2193 | - |
|
| 2194 | - if (!$abitem) { |
|
| 2195 | - return ''; |
|
| 2196 | - } |
|
| 2197 | - |
|
| 2198 | - $props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]); |
|
| 2199 | - |
|
| 2200 | - if ($props[PR_ADDRTYPE] == 'SMTP') { |
|
| 2201 | - return $props[PR_EMAIL_ADDRESS]; |
|
| 2202 | - } |
|
| 2203 | - |
|
| 2204 | - return $props[PR_SMTP_ADDRESS]; |
|
| 2205 | - } |
|
| 2206 | - |
|
| 2207 | - /** |
|
| 2208 | - * Gets the properties associated with the owner of the passed store: |
|
| 2209 | - * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY. |
|
| 2210 | - * |
|
| 2211 | - * @param $store message store |
|
| 2212 | - * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner |
|
| 2213 | - * not used when passed store is public store. for public store we are always returning logged in user's info. |
|
| 2214 | - * |
|
| 2215 | - * @return properties of logged in user in an array in sequence of display_name, email address, address type, |
|
| 2216 | - * entryid and search key |
|
| 2217 | - */ |
|
| 2218 | - public function getOwnerAddress($store, $fallbackToLoggedInUser = true) { |
|
| 2219 | - if (!$this->session) { |
|
| 2220 | - return false; |
|
| 2221 | - } |
|
| 2222 | - |
|
| 2223 | - $storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]); |
|
| 2224 | - |
|
| 2225 | - $ownerEntryId = false; |
|
| 2226 | - if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) { |
|
| 2227 | - $ownerEntryId = $storeProps[PR_USER_ENTRYID]; |
|
| 2228 | - } |
|
| 2229 | - |
|
| 2230 | - if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) { |
|
| 2231 | - $ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID]; |
|
| 2232 | - } |
|
| 2233 | - |
|
| 2234 | - if ($ownerEntryId) { |
|
| 2235 | - $ab = mapi_openaddressbook($this->session); |
|
| 2236 | - |
|
| 2237 | - $zarafaUser = mapi_ab_openentry($ab, $ownerEntryId); |
|
| 2238 | - if (!$zarafaUser) { |
|
| 2239 | - return false; |
|
| 2240 | - } |
|
| 2241 | - |
|
| 2242 | - $ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]); |
|
| 2243 | - |
|
| 2244 | - $addrType = $ownerProps[PR_ADDRTYPE]; |
|
| 2245 | - $name = $ownerProps[PR_DISPLAY_NAME]; |
|
| 2246 | - $emailAddr = $ownerProps[PR_EMAIL_ADDRESS]; |
|
| 2247 | - $searchKey = $ownerProps[PR_SEARCH_KEY]; |
|
| 2248 | - $entryId = $ownerEntryId; |
|
| 2249 | - |
|
| 2250 | - return [$name, $emailAddr, $addrType, $entryId, $searchKey]; |
|
| 2251 | - } |
|
| 2252 | - |
|
| 2253 | - return false; |
|
| 2254 | - } |
|
| 2255 | - |
|
| 2256 | - // Opens this session's default message store |
|
| 2257 | - public function openDefaultStore() { |
|
| 2258 | - $storestable = mapi_getmsgstorestable($this->session); |
|
| 2259 | - $rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]); |
|
| 2260 | - |
|
| 2261 | - foreach ($rows as $row) { |
|
| 2262 | - if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) { |
|
| 2263 | - $entryid = $row[PR_ENTRYID]; |
|
| 2264 | - |
|
| 2265 | - break; |
|
| 2266 | - } |
|
| 2267 | - } |
|
| 2268 | - |
|
| 2269 | - if (!$entryid) { |
|
| 2270 | - return false; |
|
| 2271 | - } |
|
| 2272 | - |
|
| 2273 | - return mapi_openmsgstore($this->session, $entryid); |
|
| 2274 | - } |
|
| 2275 | - |
|
| 2276 | - /** |
|
| 2277 | - * Function which adds organizer to recipient list which is passed. |
|
| 2278 | - * This function also checks if it has organizer. |
|
| 2279 | - * |
|
| 2280 | - * @param array $messageProps message properties |
|
| 2281 | - * @param array $recipients recipients list of message |
|
| 2282 | - * @param bool $isException true if we are processing recipient of exception |
|
| 2283 | - */ |
|
| 2284 | - public function addOrganizer($messageProps, &$recipients, $isException = false) { |
|
| 2285 | - $hasOrganizer = false; |
|
| 2286 | - // Check if meeting already has an organizer. |
|
| 2287 | - foreach ($recipients as $key => $recipient) { |
|
| 2288 | - if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
|
| 2289 | - $hasOrganizer = true; |
|
| 2290 | - } |
|
| 2291 | - elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 2292 | - // Recipients for an occurrence |
|
| 2293 | - $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
|
| 2294 | - } |
|
| 2295 | - } |
|
| 2296 | - |
|
| 2297 | - if (!$hasOrganizer) { |
|
| 2298 | - // Create organizer. |
|
| 2299 | - $organizer = []; |
|
| 2300 | - $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 2301 | - $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 2302 | - $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 2303 | - $organizer[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 2304 | - $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 2305 | - $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 2306 | - $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 2307 | - $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer; |
|
| 2308 | - $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 2309 | - |
|
| 2310 | - // Add organizer to recipients list. |
|
| 2311 | - array_unshift($recipients, $organizer); |
|
| 2312 | - } |
|
| 2313 | - } |
|
| 2314 | - |
|
| 2315 | - /** |
|
| 2316 | - * Function which removes an exception/occurrence from recurrencing meeting |
|
| 2317 | - * when a meeting cancellation of an occurrence is processed. |
|
| 2318 | - * |
|
| 2319 | - * @param string $basedate basedate of an occurrence |
|
| 2320 | - * @param resource $message recurring item from which occurrence has to be deleted |
|
| 2321 | - * @param resource $store MAPI_MSG_Store which contains the item |
|
| 2322 | - */ |
|
| 2323 | - public function doRemoveExceptionFromCalendar($basedate, $message, $store) { |
|
| 2324 | - $recurr = new Recurrence($store, $message); |
|
| 2325 | - $recurr->createException([], $basedate, true); |
|
| 2326 | - mapi_savechanges($message); |
|
| 2327 | - } |
|
| 2328 | - |
|
| 2329 | - /** |
|
| 2330 | - * Function which returns basedate of an changed occurrence from globalID of meeting request. |
|
| 2331 | - * |
|
| 2332 | - *@param binary $goid globalID |
|
| 2333 | - * |
|
| 2334 | - *@return bool true if basedate is found else false it not found |
|
| 2335 | - */ |
|
| 2336 | - public function getBasedateFromGlobalID($goid) { |
|
| 2337 | - $hexguid = bin2hex($goid); |
|
| 2338 | - $hexbase = substr($hexguid, 32, 8); |
|
| 2339 | - $day = hexdec(substr($hexbase, 6, 2)); |
|
| 2340 | - $month = hexdec(substr($hexbase, 4, 2)); |
|
| 2341 | - $year = hexdec(substr($hexbase, 0, 4)); |
|
| 2342 | - |
|
| 2343 | - if ($day && $month && $year) { |
|
| 2344 | - return gmmktime(0, 0, 0, $month, $day, $year); |
|
| 2345 | - } |
|
| 2346 | - |
|
| 2347 | - return false; |
|
| 2348 | - } |
|
| 2349 | - |
|
| 2350 | - /** |
|
| 2351 | - * Function which sets basedate in globalID of changed occurrence which is to be send. |
|
| 2352 | - * |
|
| 2353 | - *@param binary $goid globalID |
|
| 2354 | - *@param string basedate of changed occurrence |
|
| 2355 | - * @param mixed $basedate |
|
| 2356 | - * |
|
| 2357 | - *@return binary globalID with basedate in it |
|
| 2358 | - */ |
|
| 2359 | - public function setBasedateInGlobalID($goid, $basedate = false) { |
|
| 2360 | - $hexguid = bin2hex($goid); |
|
| 2361 | - $year = $basedate ? sprintf('%04s', dechex(gmdate('Y', $basedate))) : '0000'; |
|
| 2362 | - $month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00'; |
|
| 2363 | - $day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00'; |
|
| 2364 | - |
|
| 2365 | - return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40))); |
|
| 2366 | - } |
|
| 2367 | - |
|
| 2368 | - /** |
|
| 2369 | - * Function which replaces attachments with copy_from in copy_to. |
|
| 2370 | - * |
|
| 2371 | - * @param MAPIMessage $copy_from MAPI_message from which attachments are to be copied |
|
| 2372 | - * @param MAPIMessage $copy_to MAPI_message to which attachment are to be copied |
|
| 2373 | - * @param bool $copyExceptions if true then all exceptions should also be sent as attachments |
|
| 2374 | - * @param mixed $copyFrom |
|
| 2375 | - * @param mixed $copyTo |
|
| 2376 | - */ |
|
| 2377 | - public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true) { |
|
| 2378 | - /* remove all old attachments */ |
|
| 2379 | - $attachmentTable = mapi_message_getattachmenttable($copyTo); |
|
| 2380 | - if ($attachmentTable) { |
|
| 2381 | - $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]); |
|
| 2382 | - |
|
| 2383 | - foreach ($attachments as $attachProps) { |
|
| 2384 | - /* remove exceptions too? */ |
|
| 2385 | - if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) { |
|
| 2386 | - continue; |
|
| 2387 | - } |
|
| 2388 | - mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]); |
|
| 2389 | - } |
|
| 2390 | - } |
|
| 2391 | - $attachmentTable = false; |
|
| 2392 | - |
|
| 2393 | - /* copy new attachments */ |
|
| 2394 | - $attachmentTable = mapi_message_getattachmenttable($copyFrom); |
|
| 2395 | - if ($attachmentTable) { |
|
| 2396 | - $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]); |
|
| 2397 | - |
|
| 2398 | - foreach ($attachments as $attachProps) { |
|
| 2399 | - if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) { |
|
| 2400 | - continue; |
|
| 2401 | - } |
|
| 2402 | - |
|
| 2403 | - $attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]); |
|
| 2404 | - $attachNewResourceMsg = mapi_message_createattach($copyTo); |
|
| 2405 | - mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0); |
|
| 2406 | - mapi_savechanges($attachNewResourceMsg); |
|
| 2407 | - } |
|
| 2408 | - } |
|
| 2409 | - } |
|
| 2410 | - |
|
| 2411 | - /** |
|
| 2412 | - * Function which replaces recipients in copy_to with recipients from copyFrom. |
|
| 2413 | - * |
|
| 2414 | - * @param MAPIMessage $copyFrom MAPI_message from which recipients are to be copied |
|
| 2415 | - * @param MAPIMessage $copyTo MAPI_message to which recipients are to be copied |
|
| 2416 | - * @param bool $isDelegate indicates delegate is processing |
|
| 2417 | - * so don't copy delegate information to recipient table |
|
| 2418 | - */ |
|
| 2419 | - public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false) { |
|
| 2420 | - $recipientTable = mapi_message_getrecipienttable($copyFrom); |
|
| 2421 | - |
|
| 2422 | - // If delegate, then do not add the delegate in recipients |
|
| 2423 | - if ($isDelegate) { |
|
| 2424 | - $delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]); |
|
| 2425 | - $res = [RES_PROPERTY, [ |
|
| 2426 | - RELOP => RELOP_NE, |
|
| 2427 | - ULPROPTAG => PR_EMAIL_ADDRESS, |
|
| 2428 | - VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]], |
|
| 2429 | - ], |
|
| 2430 | - ]; |
|
| 2431 | - $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res); |
|
| 2432 | - } |
|
| 2433 | - else { |
|
| 2434 | - $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 2435 | - } |
|
| 2436 | - |
|
| 2437 | - $copyToRecipientTable = mapi_message_getrecipienttable($copyTo); |
|
| 2438 | - $copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [PR_ROWID]); |
|
| 2439 | - |
|
| 2440 | - mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows); |
|
| 2441 | - mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients); |
|
| 2442 | - } |
|
| 2443 | - |
|
| 2444 | - /** |
|
| 2445 | - * Function creates meeting item in resource's calendar. |
|
| 2446 | - * |
|
| 2447 | - * @param resource $message MAPI_message which is to create in resource's calendar |
|
| 2448 | - * @param bool $cancel cancel meeting |
|
| 2449 | - * @param string $prefix prefix for subject of meeting |
|
| 2450 | - * @param mixed $basedate |
|
| 2451 | - */ |
|
| 2452 | - public function bookResources($message, $cancel, $prefix, $basedate = false) { |
|
| 2453 | - if (!$this->enableDirectBooking) { |
|
| 2454 | - return []; |
|
| 2455 | - } |
|
| 2456 | - |
|
| 2457 | - // Get the properties of the message |
|
| 2458 | - $messageprops = mapi_getprops($message); |
|
| 2459 | - |
|
| 2460 | - if ($basedate) { |
|
| 2461 | - $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]); |
|
| 2462 | - |
|
| 2463 | - $messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate); |
|
| 2464 | - $messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']]; |
|
| 2465 | - |
|
| 2466 | - // Delete properties which are not needed. |
|
| 2467 | - $deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD]; |
|
| 2468 | - foreach ($deleteProps as $propID) { |
|
| 2469 | - if (isset($messageprops[$propID])) { |
|
| 2470 | - unset($messageprops[$propID]); |
|
| 2471 | - } |
|
| 2472 | - } |
|
| 2473 | - |
|
| 2474 | - if (isset($messageprops[$this->proptags['recurring']])) { |
|
| 2475 | - $messageprops[$this->proptags['recurring']] = false; |
|
| 2476 | - } |
|
| 2477 | - |
|
| 2478 | - // Set Outlook properties |
|
| 2479 | - $messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']]; |
|
| 2480 | - $messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']]; |
|
| 2481 | - $messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']]; |
|
| 2482 | - $messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']]; |
|
| 2483 | - $messageprops[$this->proptags['attendee_critical_change']] = time(); |
|
| 2484 | - $messageprops[$this->proptags['owner_critical_change']] = time(); |
|
| 2485 | - } |
|
| 2486 | - |
|
| 2487 | - // Get resource recipients |
|
| 2488 | - $getResourcesRestriction = [ |
|
| 2489 | - RES_AND, |
|
| 2490 | - [ |
|
| 2491 | - [ |
|
| 2492 | - RES_PROPERTY, |
|
| 2493 | - [ |
|
| 2494 | - RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2495 | - ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 2496 | - VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 2497 | - ], |
|
| 2498 | - ], ], |
|
| 2499 | - ]; |
|
| 2500 | - $recipienttable = mapi_message_getrecipienttable($message); |
|
| 2501 | - $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction); |
|
| 2502 | - |
|
| 2503 | - $this->errorSetResource = false; |
|
| 2504 | - $resourceRecipData = []; |
|
| 2505 | - |
|
| 2506 | - // Put appointment into store resource users |
|
| 2507 | - $i = 0; |
|
| 2508 | - $len = count($resourceRecipients); |
|
| 2509 | - while (!$this->errorSetResource && $i < $len) { |
|
| 2510 | - $userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]); |
|
| 2511 | - |
|
| 2512 | - // Open root folder |
|
| 2513 | - $userRoot = mapi_msgstore_openentry($userStore, null); |
|
| 2514 | - |
|
| 2515 | - // Get calendar entryID |
|
| 2516 | - $userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]); |
|
| 2517 | - |
|
| 2518 | - // Open Calendar folder |
|
| 2519 | - $accessToFolder = false; |
|
| 2520 | - |
|
| 2521 | - try { |
|
| 2522 | - // @FIXME this checks delegate has access to resource's calendar folder |
|
| 2523 | - // but it should use boss' credentials |
|
| 2524 | - |
|
| 2525 | - $accessToFolder = $this->checkCalendarWriteAccess($this->store); |
|
| 2526 | - if ($accessToFolder) { |
|
| 2527 | - $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 2528 | - } |
|
| 2529 | - } |
|
| 2530 | - catch (MAPIException $e) { |
|
| 2531 | - $e->setHandled(); |
|
| 2532 | - $this->errorSetResource = 1; // No access |
|
| 2533 | - } |
|
| 2534 | - |
|
| 2535 | - if ($accessToFolder) { |
|
| 2536 | - /** |
|
| 2537 | - * Get the LocalFreebusy message that contains the properties that |
|
| 2538 | - * are set to accept or decline resource meeting requests. |
|
| 2539 | - */ |
|
| 2540 | - // Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg |
|
| 2541 | - $localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]); |
|
| 2542 | - if ($localFreebusyMsg) { |
|
| 2543 | - $props = mapi_getprops($localFreebusyMsg, [PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS]); |
|
| 2544 | - |
|
| 2545 | - $acceptMeetingRequests = isset($props[PR_PROCESS_MEETING_REQUESTS]) ? $props[PR_PROCESS_MEETING_REQUESTS] : false; |
|
| 2546 | - $declineRecurringMeetingRequests = isset($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? $props[PR_DECLINE_RECURRING_MEETING_REQUESTS] : false; |
|
| 2547 | - $declineConflictingMeetingRequests = isset($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? $props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS] : false; |
|
| 2548 | - |
|
| 2549 | - if (!$acceptMeetingRequests) { |
|
| 2550 | - /* |
|
| 1655 | + /** |
|
| 1656 | + * Return the tracking status of a recipient based on the IPM class (passed). |
|
| 1657 | + * |
|
| 1658 | + * @param mixed $class |
|
| 1659 | + */ |
|
| 1660 | + public function getTrackStatus($class) { |
|
| 1661 | + $status = olRecipientTrackStatusNone; |
|
| 1662 | + |
|
| 1663 | + switch ($class) { |
|
| 1664 | + case 'IPM.Schedule.Meeting.Resp.Pos': |
|
| 1665 | + $status = olRecipientTrackStatusAccepted; |
|
| 1666 | + |
|
| 1667 | + break; |
|
| 1668 | + |
|
| 1669 | + case 'IPM.Schedule.Meeting.Resp.Tent': |
|
| 1670 | + $status = olRecipientTrackStatusTentative; |
|
| 1671 | + |
|
| 1672 | + break; |
|
| 1673 | + |
|
| 1674 | + case 'IPM.Schedule.Meeting.Resp.Neg': |
|
| 1675 | + $status = olRecipientTrackStatusDeclined; |
|
| 1676 | + |
|
| 1677 | + break; |
|
| 1678 | + } |
|
| 1679 | + |
|
| 1680 | + return $status; |
|
| 1681 | + } |
|
| 1682 | + |
|
| 1683 | + /** |
|
| 1684 | + * Function returns MAPIFolder resource of the folder that currently holds this meeting/meeting request |
|
| 1685 | + * object. |
|
| 1686 | + */ |
|
| 1687 | + public function openParentFolder() { |
|
| 1688 | + $messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]); |
|
| 1689 | + |
|
| 1690 | + return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]); |
|
| 1691 | + } |
|
| 1692 | + |
|
| 1693 | + /** |
|
| 1694 | + * Function will return resource of the default calendar folder of store. |
|
| 1695 | + * |
|
| 1696 | + * @param MAPIStore $store {optional} user store whose default calendar should be opened |
|
| 1697 | + * |
|
| 1698 | + * @return MAPIFolder default calendar folder of store |
|
| 1699 | + */ |
|
| 1700 | + public function openDefaultCalendar($store = false) { |
|
| 1701 | + return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID, $store); |
|
| 1702 | + } |
|
| 1703 | + |
|
| 1704 | + /** |
|
| 1705 | + * Function will return resource of the default outbox folder of store. |
|
| 1706 | + * |
|
| 1707 | + * @param MAPIStore $store {optional} user store whose default outbox should be opened |
|
| 1708 | + * |
|
| 1709 | + * @return MAPIFolder default outbox folder of store |
|
| 1710 | + */ |
|
| 1711 | + public function openDefaultOutbox($store = false) { |
|
| 1712 | + return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store); |
|
| 1713 | + } |
|
| 1714 | + |
|
| 1715 | + /** |
|
| 1716 | + * Function will return resource of the default wastebasket folder of store. |
|
| 1717 | + * |
|
| 1718 | + * @param MAPIStore $store {optional} user store whose default wastebasket should be opened |
|
| 1719 | + * |
|
| 1720 | + * @return MAPIFolder default wastebasket folder of store |
|
| 1721 | + */ |
|
| 1722 | + public function openDefaultWastebasket($store = false) { |
|
| 1723 | + return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID, $store); |
|
| 1724 | + } |
|
| 1725 | + |
|
| 1726 | + /** |
|
| 1727 | + * Function will return resource of the default calendar folder of store. |
|
| 1728 | + * |
|
| 1729 | + * @param MAPIStore $store {optional} user store whose default calendar should be opened |
|
| 1730 | + * |
|
| 1731 | + * @return MAPIFolder default calendar folder of store |
|
| 1732 | + */ |
|
| 1733 | + public function getDefaultWastebasketEntryID($store = false) { |
|
| 1734 | + return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID, $store); |
|
| 1735 | + } |
|
| 1736 | + |
|
| 1737 | + /** |
|
| 1738 | + * Function will return resource of the default sent mail folder of store. |
|
| 1739 | + * |
|
| 1740 | + * @param MAPIStore $store {optional} user store whose default sent mail should be opened |
|
| 1741 | + * |
|
| 1742 | + * @return MAPIFolder default sent mail folder of store |
|
| 1743 | + */ |
|
| 1744 | + public function getDefaultSentmailEntryID($store = false) { |
|
| 1745 | + return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store); |
|
| 1746 | + } |
|
| 1747 | + |
|
| 1748 | + /** |
|
| 1749 | + * Function will return entryid of any default folder of store. This method is useful when you want |
|
| 1750 | + * to get entryid of folder which is stored as properties of inbox folder |
|
| 1751 | + * (PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID). |
|
| 1752 | + * |
|
| 1753 | + * @param PropTag $prop proptag of the folder for which we want to get entryid |
|
| 1754 | + * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder |
|
| 1755 | + * |
|
| 1756 | + * @return BinString entryid of folder pointed by $prop |
|
| 1757 | + */ |
|
| 1758 | + public function getDefaultFolderEntryID($prop, $store = false) { |
|
| 1759 | + try { |
|
| 1760 | + $inbox = mapi_msgstore_getreceivefolder($store ? $store : $this->store); |
|
| 1761 | + $inboxprops = mapi_getprops($inbox, [$prop]); |
|
| 1762 | + if (isset($inboxprops[$prop])) { |
|
| 1763 | + return $inboxprops[$prop]; |
|
| 1764 | + } |
|
| 1765 | + } |
|
| 1766 | + catch (MAPIException $e) { |
|
| 1767 | + // public store doesn't support this method |
|
| 1768 | + if ($e->getCode() == MAPI_E_NO_SUPPORT) { |
|
| 1769 | + // don't propagate this error to parent handlers, if store doesn't support it |
|
| 1770 | + $e->setHandled(); |
|
| 1771 | + } |
|
| 1772 | + } |
|
| 1773 | + |
|
| 1774 | + return false; |
|
| 1775 | + } |
|
| 1776 | + |
|
| 1777 | + /** |
|
| 1778 | + * Function will return resource of any default folder of store. |
|
| 1779 | + * |
|
| 1780 | + * @param PropTag $prop proptag of the folder that we want to open |
|
| 1781 | + * @param MAPIStore $store {optional} user store from which we need to open default folder |
|
| 1782 | + * |
|
| 1783 | + * @return MAPIFolder default folder of store |
|
| 1784 | + */ |
|
| 1785 | + public function openDefaultFolder($prop, $store = false) { |
|
| 1786 | + $folder = false; |
|
| 1787 | + $entryid = $this->getDefaultFolderEntryID($prop, $store); |
|
| 1788 | + |
|
| 1789 | + if ($entryid !== false) { |
|
| 1790 | + $folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid); |
|
| 1791 | + } |
|
| 1792 | + |
|
| 1793 | + return $folder; |
|
| 1794 | + } |
|
| 1795 | + |
|
| 1796 | + /** |
|
| 1797 | + * Function will return entryid of default folder from store. This method is useful when you want |
|
| 1798 | + * to get entryid of folder which is stored as store properties |
|
| 1799 | + * (PR_IPM_FAVORITES_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID). |
|
| 1800 | + * |
|
| 1801 | + * @param PropTag $prop proptag of the folder whose entryid we want to get |
|
| 1802 | + * @param MAPIStore $store {optional} user store from which we need to get entryid of default folder |
|
| 1803 | + * |
|
| 1804 | + * @return BinString entryid of default folder from store |
|
| 1805 | + */ |
|
| 1806 | + public function getBaseEntryID($prop, $store = false) { |
|
| 1807 | + $storeprops = mapi_getprops($store ? $store : $this->store, [$prop]); |
|
| 1808 | + if (!isset($storeprops[$prop])) { |
|
| 1809 | + return false; |
|
| 1810 | + } |
|
| 1811 | + |
|
| 1812 | + return $storeprops[$prop]; |
|
| 1813 | + } |
|
| 1814 | + |
|
| 1815 | + /** |
|
| 1816 | + * Function will return resource of any default folder of store. |
|
| 1817 | + * |
|
| 1818 | + * @param PropTag $prop proptag of the folder that we want to open |
|
| 1819 | + * @param MAPIStore $store {optional} user store from which we need to open default folder |
|
| 1820 | + * |
|
| 1821 | + * @return MAPIFolder default folder of store |
|
| 1822 | + */ |
|
| 1823 | + public function openBaseFolder($prop, $store = false) { |
|
| 1824 | + $folder = false; |
|
| 1825 | + $entryid = $this->getBaseEntryID($prop, $store); |
|
| 1826 | + |
|
| 1827 | + if ($entryid !== false) { |
|
| 1828 | + $folder = mapi_msgstore_openentry($store ? $store : $this->store, $entryid); |
|
| 1829 | + } |
|
| 1830 | + |
|
| 1831 | + return $folder; |
|
| 1832 | + } |
|
| 1833 | + |
|
| 1834 | + /** |
|
| 1835 | + * Function checks whether user has access over the specified folder or not. |
|
| 1836 | + * |
|
| 1837 | + * @param Binary $entryid entryid The entryid of the folder to check |
|
| 1838 | + * @param MAPIStore $store (optional) store from which folder should be opened |
|
| 1839 | + * |
|
| 1840 | + * @return bool true if user has an access over the folder, false if not |
|
| 1841 | + */ |
|
| 1842 | + public function checkFolderWriteAccess($entryid, $store = false) { |
|
| 1843 | + $accessToFolder = false; |
|
| 1844 | + |
|
| 1845 | + if (!empty($entryid)) { |
|
| 1846 | + if ($store === false) { |
|
| 1847 | + $store = $this->store; |
|
| 1848 | + } |
|
| 1849 | + |
|
| 1850 | + try { |
|
| 1851 | + $folder = mapi_msgstore_openentry($store, $entryid); |
|
| 1852 | + $folderProps = mapi_getprops($folder, [PR_ACCESS]); |
|
| 1853 | + if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) { |
|
| 1854 | + $accessToFolder = true; |
|
| 1855 | + } |
|
| 1856 | + } |
|
| 1857 | + catch (MAPIException $e) { |
|
| 1858 | + // we don't have rights to open folder, so return false |
|
| 1859 | + if ($e->getCode() == MAPI_E_NO_ACCESS) { |
|
| 1860 | + return $accessToFolder; |
|
| 1861 | + } |
|
| 1862 | + |
|
| 1863 | + // rethrow other errors |
|
| 1864 | + throw $e; |
|
| 1865 | + } |
|
| 1866 | + } |
|
| 1867 | + |
|
| 1868 | + return $accessToFolder; |
|
| 1869 | + } |
|
| 1870 | + |
|
| 1871 | + /** |
|
| 1872 | + * Function checks whether user has access over the specified folder or not. |
|
| 1873 | + * |
|
| 1874 | + * @param object MAPI Message Store Object |
|
| 1875 | + * @param mixed $store |
|
| 1876 | + * |
|
| 1877 | + * @return bool true if user has an access over the folder, false if not |
|
| 1878 | + */ |
|
| 1879 | + public function checkCalendarWriteAccess($store = false) { |
|
| 1880 | + if ($store === false) { |
|
| 1881 | + // If this meeting request is received by a delegate then open delegator's store. |
|
| 1882 | + $messageProps = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 1883 | + if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 1884 | + $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 1885 | + |
|
| 1886 | + $store = $delegatorStore['store']; |
|
| 1887 | + } |
|
| 1888 | + else { |
|
| 1889 | + $store = $this->store; |
|
| 1890 | + } |
|
| 1891 | + } |
|
| 1892 | + |
|
| 1893 | + // If the store is a public folder, the calendar folder is the PARENT_ENTRYID of the calendar item |
|
| 1894 | + $provider = mapi_getprops($store, [PR_MDB_PROVIDER]); |
|
| 1895 | + if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) { |
|
| 1896 | + $entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]); |
|
| 1897 | + $entryid = $entryid[PR_PARENT_ENTRYID]; |
|
| 1898 | + } |
|
| 1899 | + else { |
|
| 1900 | + $entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store); |
|
| 1901 | + if ($entryid === false) { |
|
| 1902 | + $entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store); |
|
| 1903 | + } |
|
| 1904 | + |
|
| 1905 | + if ($entryid === false) { |
|
| 1906 | + return false; |
|
| 1907 | + } |
|
| 1908 | + } |
|
| 1909 | + |
|
| 1910 | + return $this->checkFolderWriteAccess($entryid, $store); |
|
| 1911 | + } |
|
| 1912 | + |
|
| 1913 | + /** |
|
| 1914 | + * Function will resolve the user and open its store. |
|
| 1915 | + * |
|
| 1916 | + * @param string $ownerentryid the entryid of the user |
|
| 1917 | + * |
|
| 1918 | + * @return MAPIStore store of the user |
|
| 1919 | + */ |
|
| 1920 | + public function openCustomUserStore($ownerentryid) { |
|
| 1921 | + $ab = mapi_openaddressbook($this->session); |
|
| 1922 | + |
|
| 1923 | + try { |
|
| 1924 | + $mailuser = mapi_ab_openentry($ab, $ownerentryid); |
|
| 1925 | + } |
|
| 1926 | + catch (MAPIException $e) { |
|
| 1927 | + return; |
|
| 1928 | + } |
|
| 1929 | + |
|
| 1930 | + $mailuserprops = mapi_getprops($mailuser, [PR_EMAIL_ADDRESS]); |
|
| 1931 | + $storeid = mapi_msgstore_createentryid($this->store, $mailuserprops[PR_EMAIL_ADDRESS]); |
|
| 1932 | + |
|
| 1933 | + return mapi_openmsgstore($this->session, $storeid); |
|
| 1934 | + } |
|
| 1935 | + |
|
| 1936 | + /** |
|
| 1937 | + * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request. |
|
| 1938 | + * |
|
| 1939 | + * @param int $status response status of attendee |
|
| 1940 | + * @param array $proposeNewTimeProps properties of attendee's proposal |
|
| 1941 | + * @param int $basedate date of occurrence which attendee has responded |
|
| 1942 | + * @param mixed $body |
|
| 1943 | + * @param mixed $store |
|
| 1944 | + * @param mixed $calFolder |
|
| 1945 | + */ |
|
| 1946 | + public function createResponse($status, $proposeNewTimeProps = [], $body = false, $store, $basedate = false, $calFolder) { |
|
| 1947 | + $messageprops = mapi_getprops($this->message, [ |
|
| 1948 | + PR_SENT_REPRESENTING_ENTRYID, |
|
| 1949 | + PR_SENT_REPRESENTING_EMAIL_ADDRESS, |
|
| 1950 | + PR_SENT_REPRESENTING_ADDRTYPE, |
|
| 1951 | + PR_SENT_REPRESENTING_NAME, |
|
| 1952 | + PR_SENT_REPRESENTING_SEARCH_KEY, |
|
| 1953 | + $this->proptags['goid'], |
|
| 1954 | + $this->proptags['goid2'], |
|
| 1955 | + $this->proptags['location'], |
|
| 1956 | + $this->proptags['startdate'], |
|
| 1957 | + $this->proptags['duedate'], |
|
| 1958 | + $this->proptags['recurring'], |
|
| 1959 | + $this->proptags['recurring_pattern'], |
|
| 1960 | + $this->proptags['recurrence_data'], |
|
| 1961 | + $this->proptags['timezone_data'], |
|
| 1962 | + $this->proptags['timezone'], |
|
| 1963 | + $this->proptags['updatecounter'], |
|
| 1964 | + PR_SUBJECT, |
|
| 1965 | + PR_MESSAGE_CLASS, |
|
| 1966 | + PR_OWNER_APPT_ID, |
|
| 1967 | + $this->proptags['is_exception'], |
|
| 1968 | + ]); |
|
| 1969 | + |
|
| 1970 | + if ($basedate && !$this->isMeetingRequest($messageprops[PR_MESSAGE_CLASS])) { |
|
| 1971 | + // we are creating response from a recurring calendar item object |
|
| 1972 | + // We found basedate,so opened occurrence and get properties. |
|
| 1973 | + $recurr = new Recurrence($store, $this->message); |
|
| 1974 | + $exception = $recurr->getExceptionAttachment($basedate); |
|
| 1975 | + |
|
| 1976 | + if ($exception) { |
|
| 1977 | + // Exception found, Now retrieve properties |
|
| 1978 | + $imessage = mapi_attach_openobj($exception, 0); |
|
| 1979 | + $imsgprops = mapi_getprops($imessage); |
|
| 1980 | + |
|
| 1981 | + // If location is provided, copy it to the response |
|
| 1982 | + if (isset($imsgprops[$this->proptags['location']])) { |
|
| 1983 | + $messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']]; |
|
| 1984 | + } |
|
| 1985 | + |
|
| 1986 | + // Update $messageprops with timings of occurrence |
|
| 1987 | + $messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']]; |
|
| 1988 | + $messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']]; |
|
| 1989 | + |
|
| 1990 | + // Meeting related properties |
|
| 1991 | + $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']]; |
|
| 1992 | + $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']]; |
|
| 1993 | + $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT]; |
|
| 1994 | + } |
|
| 1995 | + else { |
|
| 1996 | + // Exceptions is deleted. |
|
| 1997 | + // Update $messageprops with timings of occurrence |
|
| 1998 | + $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
|
| 1999 | + $messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
|
| 2000 | + |
|
| 2001 | + $props[$this->proptags['meetingstatus']] = olNonMeeting; |
|
| 2002 | + $props[$this->proptags['responsestatus']] = olResponseNone; |
|
| 2003 | + } |
|
| 2004 | + |
|
| 2005 | + $props[$this->proptags['recurring']] = false; |
|
| 2006 | + $props[$this->proptags['is_exception']] = true; |
|
| 2007 | + } |
|
| 2008 | + else { |
|
| 2009 | + // we are creating a response from meeting request mail (it could be recurring or non-recurring) |
|
| 2010 | + // Send all recurrence info in response, if this is a recurrence meeting. |
|
| 2011 | + $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]; |
|
| 2012 | + $isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']]; |
|
| 2013 | + if ($isRecurring || $isException) { |
|
| 2014 | + if ($isRecurring) { |
|
| 2015 | + $props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']]; |
|
| 2016 | + } |
|
| 2017 | + if ($isException) { |
|
| 2018 | + $props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']]; |
|
| 2019 | + } |
|
| 2020 | + $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder); |
|
| 2021 | + |
|
| 2022 | + $calendaritem = mapi_msgstore_openentry($store, $calendaritems[0]); |
|
| 2023 | + $recurr = new Recurrence($store, $calendaritem); |
|
| 2024 | + } |
|
| 2025 | + } |
|
| 2026 | + |
|
| 2027 | + // we are sending a response for recurring meeting request (or exception), so set some required properties |
|
| 2028 | + if (isset($recurr) && $recurr) { |
|
| 2029 | + if (!empty($messageprops[$this->proptags['recurring_pattern']])) { |
|
| 2030 | + $props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']]; |
|
| 2031 | + } |
|
| 2032 | + |
|
| 2033 | + if (!empty($messageprops[$this->proptags['recurrence_data']])) { |
|
| 2034 | + $props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']]; |
|
| 2035 | + } |
|
| 2036 | + |
|
| 2037 | + $props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']]; |
|
| 2038 | + $props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']]; |
|
| 2039 | + |
|
| 2040 | + $this->generateRecurDates($recurr, $messageprops, $props); |
|
| 2041 | + } |
|
| 2042 | + |
|
| 2043 | + // Create a response message |
|
| 2044 | + $recip = []; |
|
| 2045 | + $recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 2046 | + $recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 2047 | + $recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 2048 | + $recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME]; |
|
| 2049 | + $recip[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 2050 | + $recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 2051 | + |
|
| 2052 | + switch ($status) { |
|
| 2053 | + case olResponseAccepted: |
|
| 2054 | + $classpostfix = 'Pos'; |
|
| 2055 | + $subjectprefix = _('Accepted'); |
|
| 2056 | + |
|
| 2057 | + break; |
|
| 2058 | + |
|
| 2059 | + case olResponseDeclined: |
|
| 2060 | + $classpostfix = 'Neg'; |
|
| 2061 | + $subjectprefix = _('Declined'); |
|
| 2062 | + |
|
| 2063 | + break; |
|
| 2064 | + |
|
| 2065 | + case olResponseTentative: |
|
| 2066 | + $classpostfix = 'Tent'; |
|
| 2067 | + $subjectprefix = _('Tentatively accepted'); |
|
| 2068 | + |
|
| 2069 | + break; |
|
| 2070 | + } |
|
| 2071 | + |
|
| 2072 | + if (!empty($proposeNewTimeProps)) { |
|
| 2073 | + // if attendee has proposed new time then change subject prefix |
|
| 2074 | + $subjectprefix = _('New Time Proposed'); |
|
| 2075 | + } |
|
| 2076 | + |
|
| 2077 | + $props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT]; |
|
| 2078 | + |
|
| 2079 | + $props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix; |
|
| 2080 | + if (isset($messageprops[PR_OWNER_APPT_ID])) { |
|
| 2081 | + $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID]; |
|
| 2082 | + } |
|
| 2083 | + |
|
| 2084 | + // Set GlobalId AND CleanGlobalId, if exception then also set basedate into GlobalId(0x3). |
|
| 2085 | + $props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate); |
|
| 2086 | + $props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']]; |
|
| 2087 | + $props[$this->proptags['updatecounter']] = isset($messageprops[$this->proptags['updatecounter']]) ? $messageprops[$this->proptags['updatecounter']] : 0; |
|
| 2088 | + |
|
| 2089 | + if (!empty($proposeNewTimeProps)) { |
|
| 2090 | + // merge proposal properties to message properties which will be sent to organizer |
|
| 2091 | + $props = $proposeNewTimeProps + $props; |
|
| 2092 | + } |
|
| 2093 | + |
|
| 2094 | + // Set body message in Appointment |
|
| 2095 | + if (isset($body)) { |
|
| 2096 | + $props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body; |
|
| 2097 | + } |
|
| 2098 | + |
|
| 2099 | + // PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message |
|
| 2100 | + $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']]; |
|
| 2101 | + $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']]; |
|
| 2102 | + |
|
| 2103 | + // Set startdate and duedate in response mail. |
|
| 2104 | + $props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']]; |
|
| 2105 | + $props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']]; |
|
| 2106 | + |
|
| 2107 | + // responselocation is used in the UI in Outlook on the response message |
|
| 2108 | + if (isset($messageprops[$this->proptags['location']])) { |
|
| 2109 | + $props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']]; |
|
| 2110 | + $props[$this->proptags['location']] = $messageprops[$this->proptags['location']]; |
|
| 2111 | + } |
|
| 2112 | + |
|
| 2113 | + $message = $this->createOutgoingMessage($store); |
|
| 2114 | + |
|
| 2115 | + mapi_setprops($message, $props); |
|
| 2116 | + mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]); |
|
| 2117 | + mapi_savechanges($message); |
|
| 2118 | + mapi_message_submitmessage($message); |
|
| 2119 | + } |
|
| 2120 | + |
|
| 2121 | + /** |
|
| 2122 | + * Function which finds items in calendar based on globalId and cleanGlobalId. |
|
| 2123 | + * |
|
| 2124 | + * @param binary $goid GlobalID(0x3) of item |
|
| 2125 | + * @param MAPIFolder $calendar MAPI_folder of user (optional) |
|
| 2126 | + * @param bool $useCleanGlobalId if true then search should be performed on cleanGlobalId(0x23) else globalId(0x3) |
|
| 2127 | + */ |
|
| 2128 | + public function findCalendarItems($goid, $calendar = false, $useCleanGlobalId = false) { |
|
| 2129 | + if ($calendar === false) { |
|
| 2130 | + // Open the Calendar |
|
| 2131 | + $calendar = $this->openDefaultCalendar(); |
|
| 2132 | + } |
|
| 2133 | + |
|
| 2134 | + // Find the item by restricting all items to the correct ID |
|
| 2135 | + $restrict = [ |
|
| 2136 | + RES_AND, |
|
| 2137 | + [ |
|
| 2138 | + [ |
|
| 2139 | + RES_PROPERTY, |
|
| 2140 | + [ |
|
| 2141 | + RELOP => RELOP_EQ, |
|
| 2142 | + ULPROPTAG => ($useCleanGlobalId === true ? $this->proptags['goid2'] : $this->proptags['goid']), |
|
| 2143 | + VALUE => $goid, |
|
| 2144 | + ], |
|
| 2145 | + ], |
|
| 2146 | + ], ]; |
|
| 2147 | + |
|
| 2148 | + $calendarcontents = mapi_folder_getcontentstable($calendar); |
|
| 2149 | + |
|
| 2150 | + $rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict); |
|
| 2151 | + |
|
| 2152 | + if (empty($rows)) { |
|
| 2153 | + return; |
|
| 2154 | + } |
|
| 2155 | + |
|
| 2156 | + $calendaritems = []; |
|
| 2157 | + |
|
| 2158 | + // In principle, there should only be one row, but we'll handle them all just in case |
|
| 2159 | + foreach ($rows as $row) { |
|
| 2160 | + $calendaritems[] = $row[PR_ENTRYID]; |
|
| 2161 | + } |
|
| 2162 | + |
|
| 2163 | + return $calendaritems; |
|
| 2164 | + } |
|
| 2165 | + |
|
| 2166 | + // Returns TRUE if both entryid's are equal. Equality is defined by both entryid's pointing at the |
|
| 2167 | + // same SMTP address when converted to SMTP |
|
| 2168 | + public function compareABEntryIDs($entryid1, $entryid2) { |
|
| 2169 | + // If the session was not passed, just do a 'normal' compare. |
|
| 2170 | + if (!$this->session) { |
|
| 2171 | + return $entryid1 == $entryid2; |
|
| 2172 | + } |
|
| 2173 | + |
|
| 2174 | + $smtp1 = $this->getSMTPAddress($entryid1); |
|
| 2175 | + $smtp2 = $this->getSMTPAddress($entryid2); |
|
| 2176 | + |
|
| 2177 | + if ($smtp1 == $smtp2) { |
|
| 2178 | + return true; |
|
| 2179 | + } |
|
| 2180 | + |
|
| 2181 | + return false; |
|
| 2182 | + } |
|
| 2183 | + |
|
| 2184 | + // Gets the SMTP address of the passed addressbook entryid |
|
| 2185 | + public function getSMTPAddress($entryid) { |
|
| 2186 | + if (!$this->session) { |
|
| 2187 | + return false; |
|
| 2188 | + } |
|
| 2189 | + |
|
| 2190 | + $ab = mapi_openaddressbook($this->session); |
|
| 2191 | + |
|
| 2192 | + $abitem = mapi_ab_openentry($ab, $entryid); |
|
| 2193 | + |
|
| 2194 | + if (!$abitem) { |
|
| 2195 | + return ''; |
|
| 2196 | + } |
|
| 2197 | + |
|
| 2198 | + $props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]); |
|
| 2199 | + |
|
| 2200 | + if ($props[PR_ADDRTYPE] == 'SMTP') { |
|
| 2201 | + return $props[PR_EMAIL_ADDRESS]; |
|
| 2202 | + } |
|
| 2203 | + |
|
| 2204 | + return $props[PR_SMTP_ADDRESS]; |
|
| 2205 | + } |
|
| 2206 | + |
|
| 2207 | + /** |
|
| 2208 | + * Gets the properties associated with the owner of the passed store: |
|
| 2209 | + * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY. |
|
| 2210 | + * |
|
| 2211 | + * @param $store message store |
|
| 2212 | + * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner |
|
| 2213 | + * not used when passed store is public store. for public store we are always returning logged in user's info. |
|
| 2214 | + * |
|
| 2215 | + * @return properties of logged in user in an array in sequence of display_name, email address, address type, |
|
| 2216 | + * entryid and search key |
|
| 2217 | + */ |
|
| 2218 | + public function getOwnerAddress($store, $fallbackToLoggedInUser = true) { |
|
| 2219 | + if (!$this->session) { |
|
| 2220 | + return false; |
|
| 2221 | + } |
|
| 2222 | + |
|
| 2223 | + $storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]); |
|
| 2224 | + |
|
| 2225 | + $ownerEntryId = false; |
|
| 2226 | + if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) { |
|
| 2227 | + $ownerEntryId = $storeProps[PR_USER_ENTRYID]; |
|
| 2228 | + } |
|
| 2229 | + |
|
| 2230 | + if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) { |
|
| 2231 | + $ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID]; |
|
| 2232 | + } |
|
| 2233 | + |
|
| 2234 | + if ($ownerEntryId) { |
|
| 2235 | + $ab = mapi_openaddressbook($this->session); |
|
| 2236 | + |
|
| 2237 | + $zarafaUser = mapi_ab_openentry($ab, $ownerEntryId); |
|
| 2238 | + if (!$zarafaUser) { |
|
| 2239 | + return false; |
|
| 2240 | + } |
|
| 2241 | + |
|
| 2242 | + $ownerProps = mapi_getprops($zarafaUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]); |
|
| 2243 | + |
|
| 2244 | + $addrType = $ownerProps[PR_ADDRTYPE]; |
|
| 2245 | + $name = $ownerProps[PR_DISPLAY_NAME]; |
|
| 2246 | + $emailAddr = $ownerProps[PR_EMAIL_ADDRESS]; |
|
| 2247 | + $searchKey = $ownerProps[PR_SEARCH_KEY]; |
|
| 2248 | + $entryId = $ownerEntryId; |
|
| 2249 | + |
|
| 2250 | + return [$name, $emailAddr, $addrType, $entryId, $searchKey]; |
|
| 2251 | + } |
|
| 2252 | + |
|
| 2253 | + return false; |
|
| 2254 | + } |
|
| 2255 | + |
|
| 2256 | + // Opens this session's default message store |
|
| 2257 | + public function openDefaultStore() { |
|
| 2258 | + $storestable = mapi_getmsgstorestable($this->session); |
|
| 2259 | + $rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]); |
|
| 2260 | + |
|
| 2261 | + foreach ($rows as $row) { |
|
| 2262 | + if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) { |
|
| 2263 | + $entryid = $row[PR_ENTRYID]; |
|
| 2264 | + |
|
| 2265 | + break; |
|
| 2266 | + } |
|
| 2267 | + } |
|
| 2268 | + |
|
| 2269 | + if (!$entryid) { |
|
| 2270 | + return false; |
|
| 2271 | + } |
|
| 2272 | + |
|
| 2273 | + return mapi_openmsgstore($this->session, $entryid); |
|
| 2274 | + } |
|
| 2275 | + |
|
| 2276 | + /** |
|
| 2277 | + * Function which adds organizer to recipient list which is passed. |
|
| 2278 | + * This function also checks if it has organizer. |
|
| 2279 | + * |
|
| 2280 | + * @param array $messageProps message properties |
|
| 2281 | + * @param array $recipients recipients list of message |
|
| 2282 | + * @param bool $isException true if we are processing recipient of exception |
|
| 2283 | + */ |
|
| 2284 | + public function addOrganizer($messageProps, &$recipients, $isException = false) { |
|
| 2285 | + $hasOrganizer = false; |
|
| 2286 | + // Check if meeting already has an organizer. |
|
| 2287 | + foreach ($recipients as $key => $recipient) { |
|
| 2288 | + if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
|
| 2289 | + $hasOrganizer = true; |
|
| 2290 | + } |
|
| 2291 | + elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 2292 | + // Recipients for an occurrence |
|
| 2293 | + $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
|
| 2294 | + } |
|
| 2295 | + } |
|
| 2296 | + |
|
| 2297 | + if (!$hasOrganizer) { |
|
| 2298 | + // Create organizer. |
|
| 2299 | + $organizer = []; |
|
| 2300 | + $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 2301 | + $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 2302 | + $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 2303 | + $organizer[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 2304 | + $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 2305 | + $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 2306 | + $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 2307 | + $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer; |
|
| 2308 | + $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 2309 | + |
|
| 2310 | + // Add organizer to recipients list. |
|
| 2311 | + array_unshift($recipients, $organizer); |
|
| 2312 | + } |
|
| 2313 | + } |
|
| 2314 | + |
|
| 2315 | + /** |
|
| 2316 | + * Function which removes an exception/occurrence from recurrencing meeting |
|
| 2317 | + * when a meeting cancellation of an occurrence is processed. |
|
| 2318 | + * |
|
| 2319 | + * @param string $basedate basedate of an occurrence |
|
| 2320 | + * @param resource $message recurring item from which occurrence has to be deleted |
|
| 2321 | + * @param resource $store MAPI_MSG_Store which contains the item |
|
| 2322 | + */ |
|
| 2323 | + public function doRemoveExceptionFromCalendar($basedate, $message, $store) { |
|
| 2324 | + $recurr = new Recurrence($store, $message); |
|
| 2325 | + $recurr->createException([], $basedate, true); |
|
| 2326 | + mapi_savechanges($message); |
|
| 2327 | + } |
|
| 2328 | + |
|
| 2329 | + /** |
|
| 2330 | + * Function which returns basedate of an changed occurrence from globalID of meeting request. |
|
| 2331 | + * |
|
| 2332 | + *@param binary $goid globalID |
|
| 2333 | + * |
|
| 2334 | + *@return bool true if basedate is found else false it not found |
|
| 2335 | + */ |
|
| 2336 | + public function getBasedateFromGlobalID($goid) { |
|
| 2337 | + $hexguid = bin2hex($goid); |
|
| 2338 | + $hexbase = substr($hexguid, 32, 8); |
|
| 2339 | + $day = hexdec(substr($hexbase, 6, 2)); |
|
| 2340 | + $month = hexdec(substr($hexbase, 4, 2)); |
|
| 2341 | + $year = hexdec(substr($hexbase, 0, 4)); |
|
| 2342 | + |
|
| 2343 | + if ($day && $month && $year) { |
|
| 2344 | + return gmmktime(0, 0, 0, $month, $day, $year); |
|
| 2345 | + } |
|
| 2346 | + |
|
| 2347 | + return false; |
|
| 2348 | + } |
|
| 2349 | + |
|
| 2350 | + /** |
|
| 2351 | + * Function which sets basedate in globalID of changed occurrence which is to be send. |
|
| 2352 | + * |
|
| 2353 | + *@param binary $goid globalID |
|
| 2354 | + *@param string basedate of changed occurrence |
|
| 2355 | + * @param mixed $basedate |
|
| 2356 | + * |
|
| 2357 | + *@return binary globalID with basedate in it |
|
| 2358 | + */ |
|
| 2359 | + public function setBasedateInGlobalID($goid, $basedate = false) { |
|
| 2360 | + $hexguid = bin2hex($goid); |
|
| 2361 | + $year = $basedate ? sprintf('%04s', dechex(gmdate('Y', $basedate))) : '0000'; |
|
| 2362 | + $month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00'; |
|
| 2363 | + $day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00'; |
|
| 2364 | + |
|
| 2365 | + return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40))); |
|
| 2366 | + } |
|
| 2367 | + |
|
| 2368 | + /** |
|
| 2369 | + * Function which replaces attachments with copy_from in copy_to. |
|
| 2370 | + * |
|
| 2371 | + * @param MAPIMessage $copy_from MAPI_message from which attachments are to be copied |
|
| 2372 | + * @param MAPIMessage $copy_to MAPI_message to which attachment are to be copied |
|
| 2373 | + * @param bool $copyExceptions if true then all exceptions should also be sent as attachments |
|
| 2374 | + * @param mixed $copyFrom |
|
| 2375 | + * @param mixed $copyTo |
|
| 2376 | + */ |
|
| 2377 | + public function replaceAttachments($copyFrom, $copyTo, $copyExceptions = true) { |
|
| 2378 | + /* remove all old attachments */ |
|
| 2379 | + $attachmentTable = mapi_message_getattachmenttable($copyTo); |
|
| 2380 | + if ($attachmentTable) { |
|
| 2381 | + $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]); |
|
| 2382 | + |
|
| 2383 | + foreach ($attachments as $attachProps) { |
|
| 2384 | + /* remove exceptions too? */ |
|
| 2385 | + if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) { |
|
| 2386 | + continue; |
|
| 2387 | + } |
|
| 2388 | + mapi_message_deleteattach($copyTo, $attachProps[PR_ATTACH_NUM]); |
|
| 2389 | + } |
|
| 2390 | + } |
|
| 2391 | + $attachmentTable = false; |
|
| 2392 | + |
|
| 2393 | + /* copy new attachments */ |
|
| 2394 | + $attachmentTable = mapi_message_getattachmenttable($copyFrom); |
|
| 2395 | + if ($attachmentTable) { |
|
| 2396 | + $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]); |
|
| 2397 | + |
|
| 2398 | + foreach ($attachments as $attachProps) { |
|
| 2399 | + if (!$copyExceptions && $attachProps[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG && isset($attachProps[PR_EXCEPTION_STARTTIME])) { |
|
| 2400 | + continue; |
|
| 2401 | + } |
|
| 2402 | + |
|
| 2403 | + $attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]); |
|
| 2404 | + $attachNewResourceMsg = mapi_message_createattach($copyTo); |
|
| 2405 | + mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0); |
|
| 2406 | + mapi_savechanges($attachNewResourceMsg); |
|
| 2407 | + } |
|
| 2408 | + } |
|
| 2409 | + } |
|
| 2410 | + |
|
| 2411 | + /** |
|
| 2412 | + * Function which replaces recipients in copy_to with recipients from copyFrom. |
|
| 2413 | + * |
|
| 2414 | + * @param MAPIMessage $copyFrom MAPI_message from which recipients are to be copied |
|
| 2415 | + * @param MAPIMessage $copyTo MAPI_message to which recipients are to be copied |
|
| 2416 | + * @param bool $isDelegate indicates delegate is processing |
|
| 2417 | + * so don't copy delegate information to recipient table |
|
| 2418 | + */ |
|
| 2419 | + public function replaceRecipients($copyFrom, $copyTo, $isDelegate = false) { |
|
| 2420 | + $recipientTable = mapi_message_getrecipienttable($copyFrom); |
|
| 2421 | + |
|
| 2422 | + // If delegate, then do not add the delegate in recipients |
|
| 2423 | + if ($isDelegate) { |
|
| 2424 | + $delegate = mapi_getprops($copyFrom, [PR_RECEIVED_BY_EMAIL_ADDRESS]); |
|
| 2425 | + $res = [RES_PROPERTY, [ |
|
| 2426 | + RELOP => RELOP_NE, |
|
| 2427 | + ULPROPTAG => PR_EMAIL_ADDRESS, |
|
| 2428 | + VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]], |
|
| 2429 | + ], |
|
| 2430 | + ]; |
|
| 2431 | + $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res); |
|
| 2432 | + } |
|
| 2433 | + else { |
|
| 2434 | + $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 2435 | + } |
|
| 2436 | + |
|
| 2437 | + $copyToRecipientTable = mapi_message_getrecipienttable($copyTo); |
|
| 2438 | + $copyToRecipientRows = mapi_table_queryallrows($copyToRecipientTable, [PR_ROWID]); |
|
| 2439 | + |
|
| 2440 | + mapi_message_modifyrecipients($copyTo, MODRECIP_REMOVE, $copyToRecipientRows); |
|
| 2441 | + mapi_message_modifyrecipients($copyTo, MODRECIP_ADD, $recipients); |
|
| 2442 | + } |
|
| 2443 | + |
|
| 2444 | + /** |
|
| 2445 | + * Function creates meeting item in resource's calendar. |
|
| 2446 | + * |
|
| 2447 | + * @param resource $message MAPI_message which is to create in resource's calendar |
|
| 2448 | + * @param bool $cancel cancel meeting |
|
| 2449 | + * @param string $prefix prefix for subject of meeting |
|
| 2450 | + * @param mixed $basedate |
|
| 2451 | + */ |
|
| 2452 | + public function bookResources($message, $cancel, $prefix, $basedate = false) { |
|
| 2453 | + if (!$this->enableDirectBooking) { |
|
| 2454 | + return []; |
|
| 2455 | + } |
|
| 2456 | + |
|
| 2457 | + // Get the properties of the message |
|
| 2458 | + $messageprops = mapi_getprops($message); |
|
| 2459 | + |
|
| 2460 | + if ($basedate) { |
|
| 2461 | + $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]); |
|
| 2462 | + |
|
| 2463 | + $messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate); |
|
| 2464 | + $messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']]; |
|
| 2465 | + |
|
| 2466 | + // Delete properties which are not needed. |
|
| 2467 | + $deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD]; |
|
| 2468 | + foreach ($deleteProps as $propID) { |
|
| 2469 | + if (isset($messageprops[$propID])) { |
|
| 2470 | + unset($messageprops[$propID]); |
|
| 2471 | + } |
|
| 2472 | + } |
|
| 2473 | + |
|
| 2474 | + if (isset($messageprops[$this->proptags['recurring']])) { |
|
| 2475 | + $messageprops[$this->proptags['recurring']] = false; |
|
| 2476 | + } |
|
| 2477 | + |
|
| 2478 | + // Set Outlook properties |
|
| 2479 | + $messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']]; |
|
| 2480 | + $messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']]; |
|
| 2481 | + $messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']]; |
|
| 2482 | + $messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']]; |
|
| 2483 | + $messageprops[$this->proptags['attendee_critical_change']] = time(); |
|
| 2484 | + $messageprops[$this->proptags['owner_critical_change']] = time(); |
|
| 2485 | + } |
|
| 2486 | + |
|
| 2487 | + // Get resource recipients |
|
| 2488 | + $getResourcesRestriction = [ |
|
| 2489 | + RES_AND, |
|
| 2490 | + [ |
|
| 2491 | + [ |
|
| 2492 | + RES_PROPERTY, |
|
| 2493 | + [ |
|
| 2494 | + RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2495 | + ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 2496 | + VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 2497 | + ], |
|
| 2498 | + ], ], |
|
| 2499 | + ]; |
|
| 2500 | + $recipienttable = mapi_message_getrecipienttable($message); |
|
| 2501 | + $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction); |
|
| 2502 | + |
|
| 2503 | + $this->errorSetResource = false; |
|
| 2504 | + $resourceRecipData = []; |
|
| 2505 | + |
|
| 2506 | + // Put appointment into store resource users |
|
| 2507 | + $i = 0; |
|
| 2508 | + $len = count($resourceRecipients); |
|
| 2509 | + while (!$this->errorSetResource && $i < $len) { |
|
| 2510 | + $userStore = $this->openCustomUserStore($resourceRecipients[$i][PR_ENTRYID]); |
|
| 2511 | + |
|
| 2512 | + // Open root folder |
|
| 2513 | + $userRoot = mapi_msgstore_openentry($userStore, null); |
|
| 2514 | + |
|
| 2515 | + // Get calendar entryID |
|
| 2516 | + $userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]); |
|
| 2517 | + |
|
| 2518 | + // Open Calendar folder |
|
| 2519 | + $accessToFolder = false; |
|
| 2520 | + |
|
| 2521 | + try { |
|
| 2522 | + // @FIXME this checks delegate has access to resource's calendar folder |
|
| 2523 | + // but it should use boss' credentials |
|
| 2524 | + |
|
| 2525 | + $accessToFolder = $this->checkCalendarWriteAccess($this->store); |
|
| 2526 | + if ($accessToFolder) { |
|
| 2527 | + $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 2528 | + } |
|
| 2529 | + } |
|
| 2530 | + catch (MAPIException $e) { |
|
| 2531 | + $e->setHandled(); |
|
| 2532 | + $this->errorSetResource = 1; // No access |
|
| 2533 | + } |
|
| 2534 | + |
|
| 2535 | + if ($accessToFolder) { |
|
| 2536 | + /** |
|
| 2537 | + * Get the LocalFreebusy message that contains the properties that |
|
| 2538 | + * are set to accept or decline resource meeting requests. |
|
| 2539 | + */ |
|
| 2540 | + // Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg |
|
| 2541 | + $localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]); |
|
| 2542 | + if ($localFreebusyMsg) { |
|
| 2543 | + $props = mapi_getprops($localFreebusyMsg, [PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS]); |
|
| 2544 | + |
|
| 2545 | + $acceptMeetingRequests = isset($props[PR_PROCESS_MEETING_REQUESTS]) ? $props[PR_PROCESS_MEETING_REQUESTS] : false; |
|
| 2546 | + $declineRecurringMeetingRequests = isset($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? $props[PR_DECLINE_RECURRING_MEETING_REQUESTS] : false; |
|
| 2547 | + $declineConflictingMeetingRequests = isset($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? $props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS] : false; |
|
| 2548 | + |
|
| 2549 | + if (!$acceptMeetingRequests) { |
|
| 2550 | + /* |
|
| 2551 | 2551 | * When a resource has not been set to automatically accept meeting requests, |
| 2552 | 2552 | * the meeting request has to be sent to him rather than being put directly into |
| 2553 | 2553 | * his calendar. No error should be returned. |
| 2554 | 2554 | */ |
| 2555 | - // $errorSetResource = 2; |
|
| 2556 | - $this->nonAcceptingResources[] = $resourceRecipients[$i]; |
|
| 2557 | - } |
|
| 2558 | - else { |
|
| 2559 | - if ($declineRecurringMeetingRequests && !$cancel) { |
|
| 2560 | - // Check if appointment is recurring |
|
| 2561 | - if ($messageprops[$this->proptags['recurring']]) { |
|
| 2562 | - $this->errorSetResource = 3; |
|
| 2563 | - } |
|
| 2564 | - } |
|
| 2565 | - if ($declineConflictingMeetingRequests && !$cancel) { |
|
| 2566 | - // Check for conflicting items |
|
| 2567 | - if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) { |
|
| 2568 | - $this->errorSetResource = 4; // Conflict |
|
| 2569 | - } |
|
| 2570 | - } |
|
| 2571 | - } |
|
| 2572 | - } |
|
| 2573 | - } |
|
| 2574 | - |
|
| 2575 | - if (!$this->errorSetResource && $accessToFolder) { |
|
| 2576 | - /** |
|
| 2577 | - * First search on GlobalID(0x3) |
|
| 2578 | - * 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. |
|
| 2579 | - * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID. |
|
| 2580 | - */ |
|
| 2581 | - $rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder); |
|
| 2582 | - |
|
| 2583 | - /* |
|
| 2555 | + // $errorSetResource = 2; |
|
| 2556 | + $this->nonAcceptingResources[] = $resourceRecipients[$i]; |
|
| 2557 | + } |
|
| 2558 | + else { |
|
| 2559 | + if ($declineRecurringMeetingRequests && !$cancel) { |
|
| 2560 | + // Check if appointment is recurring |
|
| 2561 | + if ($messageprops[$this->proptags['recurring']]) { |
|
| 2562 | + $this->errorSetResource = 3; |
|
| 2563 | + } |
|
| 2564 | + } |
|
| 2565 | + if ($declineConflictingMeetingRequests && !$cancel) { |
|
| 2566 | + // Check for conflicting items |
|
| 2567 | + if ($calFolder && $this->isMeetingConflicting($message, $userStore, $calFolder)) { |
|
| 2568 | + $this->errorSetResource = 4; // Conflict |
|
| 2569 | + } |
|
| 2570 | + } |
|
| 2571 | + } |
|
| 2572 | + } |
|
| 2573 | + } |
|
| 2574 | + |
|
| 2575 | + if (!$this->errorSetResource && $accessToFolder) { |
|
| 2576 | + /** |
|
| 2577 | + * First search on GlobalID(0x3) |
|
| 2578 | + * 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. |
|
| 2579 | + * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID. |
|
| 2580 | + */ |
|
| 2581 | + $rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder); |
|
| 2582 | + |
|
| 2583 | + /* |
|
| 2584 | 2584 | * If no entry is found then |
| 2585 | 2585 | * 1) Resource doesn't have meeting in Calendar. Seriously!! |
| 2586 | 2586 | * OR |
| 2587 | 2587 | * 2) We were looking for occurrence item but Resource has whole series |
| 2588 | 2588 | */ |
| 2589 | - if (empty($rows)) { |
|
| 2590 | - /** |
|
| 2591 | - * Now search on CleanGlobalID(0x23) WHY??? |
|
| 2592 | - * Because we are looking recurring item. |
|
| 2593 | - * |
|
| 2594 | - * Possible results of this search |
|
| 2595 | - * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID |
|
| 2596 | - * 2) If Resource was booked for whole series then it should return series. |
|
| 2597 | - */ |
|
| 2598 | - $rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true); |
|
| 2599 | - |
|
| 2600 | - $newResourceMsg = false; |
|
| 2601 | - if (!empty($rows)) { |
|
| 2602 | - // Since we are looking for recurring item, open every result and check for 'recurring' property. |
|
| 2603 | - foreach ($rows as $row) { |
|
| 2604 | - $ResourceMsg = mapi_msgstore_openentry($userStore, $row); |
|
| 2605 | - $ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]); |
|
| 2606 | - |
|
| 2607 | - if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) { |
|
| 2608 | - $newResourceMsg = $ResourceMsg; |
|
| 2609 | - |
|
| 2610 | - break; |
|
| 2611 | - } |
|
| 2612 | - } |
|
| 2613 | - } |
|
| 2614 | - |
|
| 2615 | - // Still no results found. I giveup, create new message. |
|
| 2616 | - if (!$newResourceMsg) { |
|
| 2617 | - $newResourceMsg = mapi_folder_createmessage($calFolder); |
|
| 2618 | - } |
|
| 2619 | - } |
|
| 2620 | - else { |
|
| 2621 | - $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]); |
|
| 2622 | - } |
|
| 2623 | - |
|
| 2624 | - // Prefix the subject if needed |
|
| 2625 | - if ($prefix && isset($messageprops[PR_SUBJECT])) { |
|
| 2626 | - $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT]; |
|
| 2627 | - } |
|
| 2628 | - |
|
| 2629 | - // Set status to cancelled if needed |
|
| 2630 | - $messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy) |
|
| 2631 | - if ($cancel) { |
|
| 2632 | - $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled |
|
| 2633 | - $messageprops[$this->proptags['busystatus']] = fbFree; // Free |
|
| 2634 | - } |
|
| 2635 | - else { |
|
| 2636 | - $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request |
|
| 2637 | - } |
|
| 2638 | - $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment |
|
| 2639 | - |
|
| 2640 | - $messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 2641 | - |
|
| 2642 | - // Remove the PR_ICON_INDEX as it is not needed in the sent message. |
|
| 2643 | - $messageprops[PR_ICON_INDEX] = null; |
|
| 2644 | - $messageprops[PR_RESPONSE_REQUESTED] = true; |
|
| 2645 | - |
|
| 2646 | - // get the store of organizer, in case of delegates it will be delegate store |
|
| 2647 | - $defaultStore = $this->openDefaultStore(); |
|
| 2648 | - |
|
| 2649 | - $storeProps = mapi_getprops($this->store, [PR_ENTRYID]); |
|
| 2650 | - $defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]); |
|
| 2651 | - |
|
| 2652 | - // @FIXME use entryid comparison functions here |
|
| 2653 | - if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) { |
|
| 2654 | - // get delegate information |
|
| 2655 | - $addrInfo = $this->getOwnerAddress($defaultStore, false); |
|
| 2656 | - |
|
| 2657 | - if ($addrInfo) { |
|
| 2658 | - list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo; |
|
| 2659 | - |
|
| 2660 | - $messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2661 | - $messageprops[PR_SENDER_NAME] = $ownername; |
|
| 2662 | - $messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 2663 | - $messageprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 2664 | - $messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 2665 | - } |
|
| 2666 | - |
|
| 2667 | - // get delegator information |
|
| 2668 | - $addrInfo = $this->getOwnerAddress($this->store, false); |
|
| 2669 | - |
|
| 2670 | - if ($addrInfo) { |
|
| 2671 | - list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo; |
|
| 2672 | - |
|
| 2673 | - $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2674 | - $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 2675 | - $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 2676 | - $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 2677 | - $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 2678 | - } |
|
| 2679 | - } |
|
| 2680 | - else { |
|
| 2681 | - // get organizer information |
|
| 2682 | - $addrinfo = $this->getOwnerAddress($this->store); |
|
| 2683 | - |
|
| 2684 | - if ($addrinfo) { |
|
| 2685 | - list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo; |
|
| 2686 | - |
|
| 2687 | - $messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2688 | - $messageprops[PR_SENDER_NAME] = $ownername; |
|
| 2689 | - $messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 2690 | - $messageprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 2691 | - $messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 2692 | - |
|
| 2693 | - $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2694 | - $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 2695 | - $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 2696 | - $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 2697 | - $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 2698 | - } |
|
| 2699 | - } |
|
| 2700 | - |
|
| 2701 | - $messageprops[$this->proptags['replytime']] = time(); |
|
| 2702 | - |
|
| 2703 | - if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) { |
|
| 2704 | - $recurr = new Recurrence($userStore, $newResourceMsg); |
|
| 2705 | - |
|
| 2706 | - // Copy recipients list |
|
| 2707 | - $reciptable = mapi_message_getrecipienttable($message); |
|
| 2708 | - $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 2709 | - |
|
| 2710 | - // add owner to recipient table |
|
| 2711 | - $this->addOrganizer($messageprops, $recips, true); |
|
| 2712 | - |
|
| 2713 | - // Update occurrence |
|
| 2714 | - if ($recurr->isException($basedate)) { |
|
| 2715 | - $recurr->modifyException($messageprops, $basedate, $recips); |
|
| 2716 | - } |
|
| 2717 | - else { |
|
| 2718 | - $recurr->createException($messageprops, $basedate, false, $recips); |
|
| 2719 | - } |
|
| 2720 | - } |
|
| 2721 | - else { |
|
| 2722 | - mapi_setprops($newResourceMsg, $messageprops); |
|
| 2723 | - |
|
| 2724 | - // Copy attachments |
|
| 2725 | - $this->replaceAttachments($message, $newResourceMsg); |
|
| 2726 | - |
|
| 2727 | - // Copy all recipients too |
|
| 2728 | - $this->replaceRecipients($message, $newResourceMsg); |
|
| 2729 | - |
|
| 2730 | - // Now add organizer also to recipient table |
|
| 2731 | - $recips = []; |
|
| 2732 | - $this->addOrganizer($messageprops, $recips); |
|
| 2733 | - |
|
| 2734 | - mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips); |
|
| 2735 | - } |
|
| 2736 | - |
|
| 2737 | - mapi_savechanges($newResourceMsg); |
|
| 2738 | - |
|
| 2739 | - $resourceRecipData[] = [ |
|
| 2740 | - 'store' => $userStore, |
|
| 2741 | - 'folder' => $calFolder, |
|
| 2742 | - 'msg' => $newResourceMsg, |
|
| 2743 | - ]; |
|
| 2744 | - $this->includesResources = true; |
|
| 2745 | - } |
|
| 2746 | - else { |
|
| 2747 | - /* |
|
| 2589 | + if (empty($rows)) { |
|
| 2590 | + /** |
|
| 2591 | + * Now search on CleanGlobalID(0x23) WHY??? |
|
| 2592 | + * Because we are looking recurring item. |
|
| 2593 | + * |
|
| 2594 | + * Possible results of this search |
|
| 2595 | + * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID |
|
| 2596 | + * 2) If Resource was booked for whole series then it should return series. |
|
| 2597 | + */ |
|
| 2598 | + $rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true); |
|
| 2599 | + |
|
| 2600 | + $newResourceMsg = false; |
|
| 2601 | + if (!empty($rows)) { |
|
| 2602 | + // Since we are looking for recurring item, open every result and check for 'recurring' property. |
|
| 2603 | + foreach ($rows as $row) { |
|
| 2604 | + $ResourceMsg = mapi_msgstore_openentry($userStore, $row); |
|
| 2605 | + $ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]); |
|
| 2606 | + |
|
| 2607 | + if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) { |
|
| 2608 | + $newResourceMsg = $ResourceMsg; |
|
| 2609 | + |
|
| 2610 | + break; |
|
| 2611 | + } |
|
| 2612 | + } |
|
| 2613 | + } |
|
| 2614 | + |
|
| 2615 | + // Still no results found. I giveup, create new message. |
|
| 2616 | + if (!$newResourceMsg) { |
|
| 2617 | + $newResourceMsg = mapi_folder_createmessage($calFolder); |
|
| 2618 | + } |
|
| 2619 | + } |
|
| 2620 | + else { |
|
| 2621 | + $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]); |
|
| 2622 | + } |
|
| 2623 | + |
|
| 2624 | + // Prefix the subject if needed |
|
| 2625 | + if ($prefix && isset($messageprops[PR_SUBJECT])) { |
|
| 2626 | + $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT]; |
|
| 2627 | + } |
|
| 2628 | + |
|
| 2629 | + // Set status to cancelled if needed |
|
| 2630 | + $messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy) |
|
| 2631 | + if ($cancel) { |
|
| 2632 | + $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled |
|
| 2633 | + $messageprops[$this->proptags['busystatus']] = fbFree; // Free |
|
| 2634 | + } |
|
| 2635 | + else { |
|
| 2636 | + $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request |
|
| 2637 | + } |
|
| 2638 | + $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment |
|
| 2639 | + |
|
| 2640 | + $messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment'; |
|
| 2641 | + |
|
| 2642 | + // Remove the PR_ICON_INDEX as it is not needed in the sent message. |
|
| 2643 | + $messageprops[PR_ICON_INDEX] = null; |
|
| 2644 | + $messageprops[PR_RESPONSE_REQUESTED] = true; |
|
| 2645 | + |
|
| 2646 | + // get the store of organizer, in case of delegates it will be delegate store |
|
| 2647 | + $defaultStore = $this->openDefaultStore(); |
|
| 2648 | + |
|
| 2649 | + $storeProps = mapi_getprops($this->store, [PR_ENTRYID]); |
|
| 2650 | + $defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]); |
|
| 2651 | + |
|
| 2652 | + // @FIXME use entryid comparison functions here |
|
| 2653 | + if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) { |
|
| 2654 | + // get delegate information |
|
| 2655 | + $addrInfo = $this->getOwnerAddress($defaultStore, false); |
|
| 2656 | + |
|
| 2657 | + if ($addrInfo) { |
|
| 2658 | + list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo; |
|
| 2659 | + |
|
| 2660 | + $messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2661 | + $messageprops[PR_SENDER_NAME] = $ownername; |
|
| 2662 | + $messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 2663 | + $messageprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 2664 | + $messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 2665 | + } |
|
| 2666 | + |
|
| 2667 | + // get delegator information |
|
| 2668 | + $addrInfo = $this->getOwnerAddress($this->store, false); |
|
| 2669 | + |
|
| 2670 | + if ($addrInfo) { |
|
| 2671 | + list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrInfo; |
|
| 2672 | + |
|
| 2673 | + $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2674 | + $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 2675 | + $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 2676 | + $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 2677 | + $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 2678 | + } |
|
| 2679 | + } |
|
| 2680 | + else { |
|
| 2681 | + // get organizer information |
|
| 2682 | + $addrinfo = $this->getOwnerAddress($this->store); |
|
| 2683 | + |
|
| 2684 | + if ($addrinfo) { |
|
| 2685 | + list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo; |
|
| 2686 | + |
|
| 2687 | + $messageprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2688 | + $messageprops[PR_SENDER_NAME] = $ownername; |
|
| 2689 | + $messageprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 2690 | + $messageprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 2691 | + $messageprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 2692 | + |
|
| 2693 | + $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 2694 | + $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 2695 | + $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 2696 | + $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 2697 | + $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 2698 | + } |
|
| 2699 | + } |
|
| 2700 | + |
|
| 2701 | + $messageprops[$this->proptags['replytime']] = time(); |
|
| 2702 | + |
|
| 2703 | + if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) { |
|
| 2704 | + $recurr = new Recurrence($userStore, $newResourceMsg); |
|
| 2705 | + |
|
| 2706 | + // Copy recipients list |
|
| 2707 | + $reciptable = mapi_message_getrecipienttable($message); |
|
| 2708 | + $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 2709 | + |
|
| 2710 | + // add owner to recipient table |
|
| 2711 | + $this->addOrganizer($messageprops, $recips, true); |
|
| 2712 | + |
|
| 2713 | + // Update occurrence |
|
| 2714 | + if ($recurr->isException($basedate)) { |
|
| 2715 | + $recurr->modifyException($messageprops, $basedate, $recips); |
|
| 2716 | + } |
|
| 2717 | + else { |
|
| 2718 | + $recurr->createException($messageprops, $basedate, false, $recips); |
|
| 2719 | + } |
|
| 2720 | + } |
|
| 2721 | + else { |
|
| 2722 | + mapi_setprops($newResourceMsg, $messageprops); |
|
| 2723 | + |
|
| 2724 | + // Copy attachments |
|
| 2725 | + $this->replaceAttachments($message, $newResourceMsg); |
|
| 2726 | + |
|
| 2727 | + // Copy all recipients too |
|
| 2728 | + $this->replaceRecipients($message, $newResourceMsg); |
|
| 2729 | + |
|
| 2730 | + // Now add organizer also to recipient table |
|
| 2731 | + $recips = []; |
|
| 2732 | + $this->addOrganizer($messageprops, $recips); |
|
| 2733 | + |
|
| 2734 | + mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips); |
|
| 2735 | + } |
|
| 2736 | + |
|
| 2737 | + mapi_savechanges($newResourceMsg); |
|
| 2738 | + |
|
| 2739 | + $resourceRecipData[] = [ |
|
| 2740 | + 'store' => $userStore, |
|
| 2741 | + 'folder' => $calFolder, |
|
| 2742 | + 'msg' => $newResourceMsg, |
|
| 2743 | + ]; |
|
| 2744 | + $this->includesResources = true; |
|
| 2745 | + } |
|
| 2746 | + else { |
|
| 2747 | + /* |
|
| 2748 | 2748 | * If no other errors occurred and you have no access to the |
| 2749 | 2749 | * folder of the resource, throw an error=1. |
| 2750 | 2750 | */ |
| 2751 | - if (!$this->errorSetResource) { |
|
| 2752 | - $this->errorSetResource = 1; |
|
| 2753 | - } |
|
| 2754 | - |
|
| 2755 | - for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) { |
|
| 2756 | - // Get the EntryID |
|
| 2757 | - $props = mapi_message_getprops($resourceRecipData[$j]['msg']); |
|
| 2758 | - |
|
| 2759 | - mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE); |
|
| 2760 | - } |
|
| 2761 | - $this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME]; |
|
| 2762 | - } |
|
| 2763 | - ++$i; |
|
| 2764 | - } |
|
| 2765 | - |
|
| 2766 | - /* |
|
| 2751 | + if (!$this->errorSetResource) { |
|
| 2752 | + $this->errorSetResource = 1; |
|
| 2753 | + } |
|
| 2754 | + |
|
| 2755 | + for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) { |
|
| 2756 | + // Get the EntryID |
|
| 2757 | + $props = mapi_message_getprops($resourceRecipData[$j]['msg']); |
|
| 2758 | + |
|
| 2759 | + mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE); |
|
| 2760 | + } |
|
| 2761 | + $this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME]; |
|
| 2762 | + } |
|
| 2763 | + ++$i; |
|
| 2764 | + } |
|
| 2765 | + |
|
| 2766 | + /* |
|
| 2767 | 2767 | * Set the BCC-recipients (resources) tackstatus to accepted. |
| 2768 | 2768 | */ |
| 2769 | - // Get resource recipients |
|
| 2770 | - $getResourcesRestriction = [ |
|
| 2771 | - RES_AND, |
|
| 2772 | - [ |
|
| 2773 | - [ |
|
| 2774 | - RES_PROPERTY, |
|
| 2775 | - [ |
|
| 2776 | - RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2777 | - ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 2778 | - VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 2779 | - ], |
|
| 2780 | - ], ], |
|
| 2781 | - ]; |
|
| 2782 | - $recipienttable = mapi_message_getrecipienttable($message); |
|
| 2783 | - $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction); |
|
| 2784 | - if (!empty($resourceRecipients)) { |
|
| 2785 | - // Set Tracking status of resource recipients to olResponseAccepted (3) |
|
| 2786 | - for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) { |
|
| 2787 | - $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted; |
|
| 2788 | - $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time(); |
|
| 2789 | - } |
|
| 2790 | - mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients); |
|
| 2791 | - } |
|
| 2792 | - |
|
| 2793 | - // Publish updated free/busy information |
|
| 2794 | - if (!$this->errorSetResource) { |
|
| 2795 | - for ($i = 0, $len = count($resourceRecipData); $i < $len; ++$i) { |
|
| 2796 | - $storeProps = mapi_getprops($resourceRecipData[$i]['store'], [PR_MAILBOX_OWNER_ENTRYID]); |
|
| 2797 | - if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) { |
|
| 2798 | - $start = time() - 7 * 24 * 60 * 60; |
|
| 2799 | - $range = strtotime("+6 month"); |
|
| 2800 | - $range = $range - (7 * 24 * 60 * 60); |
|
| 2801 | - |
|
| 2802 | - $pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]); |
|
| 2803 | - $pub->publishFB($start, $range); // publish from one week ago, 6 months ahead |
|
| 2804 | - } |
|
| 2805 | - } |
|
| 2806 | - } |
|
| 2807 | - |
|
| 2808 | - return $resourceRecipData; |
|
| 2809 | - } |
|
| 2810 | - |
|
| 2811 | - /** |
|
| 2812 | - * Function which save an exception into recurring item. |
|
| 2813 | - * |
|
| 2814 | - * @param resource $recurringItem reference to MAPI_message of recurring item |
|
| 2815 | - * @param resource $occurrenceItem reference to MAPI_message of occurrence |
|
| 2816 | - * @param string $basedate basedate of occurrence |
|
| 2817 | - * @param bool $move if true then occurrence item is deleted |
|
| 2818 | - * @param bool $tentative true if user has tentatively accepted it or false if user has accepted it |
|
| 2819 | - * @param bool $userAction true if user has manually responded to meeting request |
|
| 2820 | - * @param resource $store user store |
|
| 2821 | - * @param bool $isDelegate true if delegate is processing this meeting request |
|
| 2822 | - */ |
|
| 2823 | - public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) { |
|
| 2824 | - $recurr = new Recurrence($store, $recurringItem); |
|
| 2825 | - |
|
| 2826 | - // Copy properties from meeting request |
|
| 2827 | - $exception_props = mapi_getprops($occurrenceItem); |
|
| 2828 | - |
|
| 2829 | - // Copy recipients list |
|
| 2830 | - $reciptable = mapi_message_getrecipienttable($occurrenceItem); |
|
| 2831 | - // If delegate, then do not add the delegate in recipients |
|
| 2832 | - if ($isDelegate) { |
|
| 2833 | - $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]); |
|
| 2834 | - $res = [RES_PROPERTY, [ |
|
| 2835 | - RELOP => RELOP_NE, |
|
| 2836 | - ULPROPTAG => PR_EMAIL_ADDRESS, |
|
| 2837 | - VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]], |
|
| 2838 | - ], |
|
| 2839 | - ]; |
|
| 2840 | - $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res); |
|
| 2841 | - } |
|
| 2842 | - else { |
|
| 2843 | - $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 2844 | - } |
|
| 2845 | - |
|
| 2846 | - // add owner to recipient table |
|
| 2847 | - $this->addOrganizer($exception_props, $recips, true); |
|
| 2848 | - |
|
| 2849 | - // add delegator to meetings |
|
| 2850 | - if ($isDelegate) { |
|
| 2851 | - $this->addDelegator($exception_props, $recips); |
|
| 2852 | - } |
|
| 2853 | - |
|
| 2854 | - $exception_props[$this->proptags['meetingstatus']] = olMeetingReceived; |
|
| 2855 | - $exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 2856 | - |
|
| 2857 | - if (isset($exception_props[$this->proptags['intendedbusystatus']])) { |
|
| 2858 | - if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 2859 | - $exception_props[$this->proptags['busystatus']] = fbTentative; |
|
| 2860 | - } |
|
| 2861 | - else { |
|
| 2862 | - $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']]; |
|
| 2863 | - } |
|
| 2864 | - // we already have intendedbusystatus value in $exception_props so no need to copy it |
|
| 2865 | - } |
|
| 2866 | - else { |
|
| 2867 | - $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 2868 | - } |
|
| 2869 | - |
|
| 2870 | - if ($userAction) { |
|
| 2871 | - $addrInfo = $this->getOwnerAddress($this->store); |
|
| 2872 | - |
|
| 2873 | - // if user has responded then set replytime and name |
|
| 2874 | - $exception_props[$this->proptags['replytime']] = time(); |
|
| 2875 | - if (!empty($addrInfo)) { |
|
| 2876 | - $exception_props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 2877 | - } |
|
| 2878 | - } |
|
| 2879 | - |
|
| 2880 | - if ($recurr->isException($basedate)) { |
|
| 2881 | - $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem); |
|
| 2882 | - } |
|
| 2883 | - else { |
|
| 2884 | - $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem); |
|
| 2885 | - } |
|
| 2886 | - |
|
| 2887 | - // Move the occurrenceItem to the waste basket |
|
| 2888 | - if ($move) { |
|
| 2889 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 2890 | - $sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]); |
|
| 2891 | - mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 2892 | - } |
|
| 2893 | - |
|
| 2894 | - mapi_savechanges($recurringItem); |
|
| 2895 | - } |
|
| 2896 | - |
|
| 2897 | - /** |
|
| 2898 | - * Function which merges an exception mapi message to recurring message. |
|
| 2899 | - * This will be used when we receive recurring meeting request and we already have an exception message |
|
| 2900 | - * of same meeting in calendar and we need to remove that exception message and add it to attachment table |
|
| 2901 | - * of recurring meeting. |
|
| 2902 | - * |
|
| 2903 | - * @param resource $recurringItem reference to MAPI_message of recurring item |
|
| 2904 | - * @param resource $occurrenceItem reference to MAPI_message of occurrence |
|
| 2905 | - * @param string $basedate basedate of occurrence |
|
| 2906 | - * @param resource $store user store |
|
| 2907 | - */ |
|
| 2908 | - public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store) { |
|
| 2909 | - $recurr = new Recurrence($store, $recurringItem); |
|
| 2910 | - |
|
| 2911 | - // Copy properties from meeting request |
|
| 2912 | - $exception_props = mapi_getprops($occurrenceItem); |
|
| 2913 | - |
|
| 2914 | - // Get recipient list from message and add it to exception attachment |
|
| 2915 | - $reciptable = mapi_message_getrecipienttable($occurrenceItem); |
|
| 2916 | - $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 2917 | - |
|
| 2918 | - if ($recurr->isException($basedate)) { |
|
| 2919 | - $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem); |
|
| 2920 | - } |
|
| 2921 | - else { |
|
| 2922 | - $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem); |
|
| 2923 | - } |
|
| 2924 | - |
|
| 2925 | - // Move the occurrenceItem to the waste basket |
|
| 2926 | - $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 2927 | - $sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]); |
|
| 2928 | - mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 2929 | - |
|
| 2930 | - mapi_savechanges($recurringItem); |
|
| 2931 | - } |
|
| 2932 | - |
|
| 2933 | - /** |
|
| 2934 | - * Function which submits meeting request based on arguments passed to it. |
|
| 2935 | - * |
|
| 2936 | - * @param resource $message MAPI_message whose meeting request is to be send |
|
| 2937 | - * @param bool $cancel if true send request, else send cancellation |
|
| 2938 | - * @param string $prefix subject prefix |
|
| 2939 | - * @param int $basedate basedate for an occurrence |
|
| 2940 | - * @param object $recurObject recurrence object of mr |
|
| 2941 | - * @param bool $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments |
|
| 2942 | - * @param mixed $modifiedRecips |
|
| 2943 | - * @param mixed $deletedRecips |
|
| 2944 | - */ |
|
| 2945 | - public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false) { |
|
| 2946 | - $newmessageprops = $messageprops = mapi_getprops($this->message); |
|
| 2947 | - $new = $this->createOutgoingMessage(); |
|
| 2948 | - |
|
| 2949 | - // Copy the entire message into the new meeting request message |
|
| 2950 | - if ($basedate) { |
|
| 2951 | - // messageprops contains properties of whole recurring series |
|
| 2952 | - // and newmessageprops contains properties of exception item |
|
| 2953 | - $newmessageprops = mapi_getprops($message); |
|
| 2954 | - |
|
| 2955 | - // Ensure that the correct basedate is set in the new message |
|
| 2956 | - $newmessageprops[$this->proptags['basedate']] = $basedate; |
|
| 2957 | - |
|
| 2958 | - // Set isRecurring to false, because this is an exception |
|
| 2959 | - $newmessageprops[$this->proptags['recurring']] = false; |
|
| 2960 | - |
|
| 2961 | - // set LID_IS_EXCEPTION to true |
|
| 2962 | - $newmessageprops[$this->proptags['is_exception']] = true; |
|
| 2963 | - |
|
| 2964 | - // Set to high importance |
|
| 2965 | - if ($cancel) { |
|
| 2966 | - $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; |
|
| 2967 | - } |
|
| 2968 | - |
|
| 2969 | - // Set startdate and enddate of exception |
|
| 2970 | - if ($cancel && $recurObject) { |
|
| 2971 | - $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate); |
|
| 2972 | - $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate); |
|
| 2973 | - } |
|
| 2974 | - |
|
| 2975 | - // Set basedate in guid (0x3) |
|
| 2976 | - $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate); |
|
| 2977 | - $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']]; |
|
| 2978 | - $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID]; |
|
| 2979 | - |
|
| 2980 | - // Get deleted recipiets from exception msg |
|
| 2981 | - $restriction = [ |
|
| 2982 | - RES_AND, |
|
| 2983 | - [ |
|
| 2984 | - [ |
|
| 2985 | - RES_BITMASK, |
|
| 2986 | - [ |
|
| 2987 | - ULTYPE => BMR_NEZ, |
|
| 2988 | - ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 2989 | - ULMASK => recipExceptionalDeleted, |
|
| 2990 | - ], |
|
| 2991 | - ], |
|
| 2992 | - [ |
|
| 2993 | - RES_BITMASK, |
|
| 2994 | - [ |
|
| 2995 | - ULTYPE => BMR_EQZ, |
|
| 2996 | - ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 2997 | - ULMASK => recipOrganizer, |
|
| 2998 | - ], |
|
| 2999 | - ], |
|
| 3000 | - ], |
|
| 3001 | - ]; |
|
| 3002 | - |
|
| 3003 | - // In direct-booking mode, we don't need to send cancellations to resources |
|
| 3004 | - if ($this->enableDirectBooking) { |
|
| 3005 | - $restriction[1][] = [ |
|
| 3006 | - RES_PROPERTY, |
|
| 3007 | - [ |
|
| 3008 | - RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3009 | - ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 3010 | - VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 3011 | - ], |
|
| 3012 | - ]; |
|
| 3013 | - } |
|
| 3014 | - |
|
| 3015 | - $recipienttable = mapi_message_getrecipienttable($message); |
|
| 3016 | - $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction); |
|
| 3017 | - |
|
| 3018 | - if (!$deletedRecips) { |
|
| 3019 | - $deletedRecips = array_merge([], $recipients); |
|
| 3020 | - } |
|
| 3021 | - else { |
|
| 3022 | - $deletedRecips = array_merge($deletedRecips, $recipients); |
|
| 3023 | - } |
|
| 3024 | - } |
|
| 3025 | - |
|
| 3026 | - // Remove the PR_ICON_INDEX as it is not needed in the sent message. |
|
| 3027 | - $newmessageprops[PR_ICON_INDEX] = null; |
|
| 3028 | - $newmessageprops[PR_RESPONSE_REQUESTED] = true; |
|
| 3029 | - |
|
| 3030 | - // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar |
|
| 3031 | - $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']]; |
|
| 3032 | - $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']]; |
|
| 3033 | - |
|
| 3034 | - // Set updatecounter/AppointmentSequenceNumber |
|
| 3035 | - // get the value of latest updatecounter for the whole series and use it |
|
| 3036 | - $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']]; |
|
| 3037 | - |
|
| 3038 | - $meetingTimeInfo = $this->getMeetingTimeInfo(); |
|
| 3039 | - |
|
| 3040 | - if ($meetingTimeInfo) { |
|
| 3041 | - $newmessageprops[PR_BODY] = $meetingTimeInfo; |
|
| 3042 | - } |
|
| 3043 | - |
|
| 3044 | - // Send all recurrence info in mail, if this is a recurrence meeting. |
|
| 3045 | - if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) { |
|
| 3046 | - if (!empty($messageprops[$this->proptags['recurring_pattern']])) { |
|
| 3047 | - $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']]; |
|
| 3048 | - } |
|
| 3049 | - $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']]; |
|
| 3050 | - $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']]; |
|
| 3051 | - $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']]; |
|
| 3052 | - |
|
| 3053 | - if ($recurObject) { |
|
| 3054 | - $this->generateRecurDates($recurObject, $messageprops, $newmessageprops); |
|
| 3055 | - } |
|
| 3056 | - } |
|
| 3057 | - |
|
| 3058 | - if (isset($newmessageprops[$this->proptags['counter_proposal']])) { |
|
| 3059 | - unset($newmessageprops[$this->proptags['counter_proposal']]); |
|
| 3060 | - } |
|
| 3061 | - |
|
| 3062 | - // Prefix the subject if needed |
|
| 3063 | - if ($prefix && isset($newmessageprops[PR_SUBJECT])) { |
|
| 3064 | - $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT]; |
|
| 3065 | - } |
|
| 3066 | - |
|
| 3067 | - if (isset($newmessageprops[$this->proptags['categories']]) && |
|
| 3068 | - !empty($newmessageprops[$this->proptags['categories']])) { |
|
| 3069 | - unset($newmessageprops[$this->proptags['categories']]); |
|
| 3070 | - } |
|
| 3071 | - mapi_setprops($new, $newmessageprops); |
|
| 3072 | - |
|
| 3073 | - // Copy attachments |
|
| 3074 | - $this->replaceAttachments($message, $new, $copyExceptions); |
|
| 3075 | - |
|
| 3076 | - // Retrieve only those recipient who should receive this meeting request. |
|
| 3077 | - $stripResourcesRestriction = [ |
|
| 3078 | - RES_AND, |
|
| 3079 | - [ |
|
| 3080 | - [ |
|
| 3081 | - RES_BITMASK, |
|
| 3082 | - [ |
|
| 3083 | - ULTYPE => BMR_EQZ, |
|
| 3084 | - ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 3085 | - ULMASK => recipExceptionalDeleted, |
|
| 3086 | - ], |
|
| 3087 | - ], |
|
| 3088 | - [ |
|
| 3089 | - RES_BITMASK, |
|
| 3090 | - [ |
|
| 3091 | - ULTYPE => BMR_EQZ, |
|
| 3092 | - ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 3093 | - ULMASK => recipOrganizer, |
|
| 3094 | - ], |
|
| 3095 | - ], |
|
| 3096 | - ], |
|
| 3097 | - ]; |
|
| 3098 | - |
|
| 3099 | - // In direct-booking mode, resources do not receive a meeting request |
|
| 3100 | - if ($this->enableDirectBooking) { |
|
| 3101 | - $stripResourcesRestriction[1][] = |
|
| 3102 | - [ |
|
| 3103 | - RES_PROPERTY, |
|
| 3104 | - [ |
|
| 3105 | - RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3106 | - ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 3107 | - VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 3108 | - ], |
|
| 3109 | - ]; |
|
| 3110 | - } |
|
| 3111 | - |
|
| 3112 | - // If no recipients were explicitly provided, we will send the update to all |
|
| 3113 | - // recipients from the meeting. |
|
| 3114 | - if ($modifiedRecips === false) { |
|
| 3115 | - $recipienttable = mapi_message_getrecipienttable($message); |
|
| 3116 | - $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction); |
|
| 3117 | - |
|
| 3118 | - if ($basedate && empty($modifiedRecips)) { |
|
| 3119 | - // Retrieve full list |
|
| 3120 | - $recipienttable = mapi_message_getrecipienttable($this->message); |
|
| 3121 | - $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops); |
|
| 3122 | - |
|
| 3123 | - // Save recipients in exceptions |
|
| 3124 | - mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips); |
|
| 3125 | - |
|
| 3126 | - // Now retrieve only those recipient who should receive this meeting request. |
|
| 3127 | - $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction); |
|
| 3128 | - } |
|
| 3129 | - } |
|
| 3130 | - |
|
| 3131 | - // @TODO: handle nonAcceptingResources |
|
| 3132 | - /* |
|
| 2769 | + // Get resource recipients |
|
| 2770 | + $getResourcesRestriction = [ |
|
| 2771 | + RES_AND, |
|
| 2772 | + [ |
|
| 2773 | + [ |
|
| 2774 | + RES_PROPERTY, |
|
| 2775 | + [ |
|
| 2776 | + RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2777 | + ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 2778 | + VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 2779 | + ], |
|
| 2780 | + ], ], |
|
| 2781 | + ]; |
|
| 2782 | + $recipienttable = mapi_message_getrecipienttable($message); |
|
| 2783 | + $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction); |
|
| 2784 | + if (!empty($resourceRecipients)) { |
|
| 2785 | + // Set Tracking status of resource recipients to olResponseAccepted (3) |
|
| 2786 | + for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) { |
|
| 2787 | + $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted; |
|
| 2788 | + $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time(); |
|
| 2789 | + } |
|
| 2790 | + mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients); |
|
| 2791 | + } |
|
| 2792 | + |
|
| 2793 | + // Publish updated free/busy information |
|
| 2794 | + if (!$this->errorSetResource) { |
|
| 2795 | + for ($i = 0, $len = count($resourceRecipData); $i < $len; ++$i) { |
|
| 2796 | + $storeProps = mapi_getprops($resourceRecipData[$i]['store'], [PR_MAILBOX_OWNER_ENTRYID]); |
|
| 2797 | + if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) { |
|
| 2798 | + $start = time() - 7 * 24 * 60 * 60; |
|
| 2799 | + $range = strtotime("+6 month"); |
|
| 2800 | + $range = $range - (7 * 24 * 60 * 60); |
|
| 2801 | + |
|
| 2802 | + $pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]); |
|
| 2803 | + $pub->publishFB($start, $range); // publish from one week ago, 6 months ahead |
|
| 2804 | + } |
|
| 2805 | + } |
|
| 2806 | + } |
|
| 2807 | + |
|
| 2808 | + return $resourceRecipData; |
|
| 2809 | + } |
|
| 2810 | + |
|
| 2811 | + /** |
|
| 2812 | + * Function which save an exception into recurring item. |
|
| 2813 | + * |
|
| 2814 | + * @param resource $recurringItem reference to MAPI_message of recurring item |
|
| 2815 | + * @param resource $occurrenceItem reference to MAPI_message of occurrence |
|
| 2816 | + * @param string $basedate basedate of occurrence |
|
| 2817 | + * @param bool $move if true then occurrence item is deleted |
|
| 2818 | + * @param bool $tentative true if user has tentatively accepted it or false if user has accepted it |
|
| 2819 | + * @param bool $userAction true if user has manually responded to meeting request |
|
| 2820 | + * @param resource $store user store |
|
| 2821 | + * @param bool $isDelegate true if delegate is processing this meeting request |
|
| 2822 | + */ |
|
| 2823 | + public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) { |
|
| 2824 | + $recurr = new Recurrence($store, $recurringItem); |
|
| 2825 | + |
|
| 2826 | + // Copy properties from meeting request |
|
| 2827 | + $exception_props = mapi_getprops($occurrenceItem); |
|
| 2828 | + |
|
| 2829 | + // Copy recipients list |
|
| 2830 | + $reciptable = mapi_message_getrecipienttable($occurrenceItem); |
|
| 2831 | + // If delegate, then do not add the delegate in recipients |
|
| 2832 | + if ($isDelegate) { |
|
| 2833 | + $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]); |
|
| 2834 | + $res = [RES_PROPERTY, [ |
|
| 2835 | + RELOP => RELOP_NE, |
|
| 2836 | + ULPROPTAG => PR_EMAIL_ADDRESS, |
|
| 2837 | + VALUE => [PR_EMAIL_ADDRESS => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]], |
|
| 2838 | + ], |
|
| 2839 | + ]; |
|
| 2840 | + $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res); |
|
| 2841 | + } |
|
| 2842 | + else { |
|
| 2843 | + $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 2844 | + } |
|
| 2845 | + |
|
| 2846 | + // add owner to recipient table |
|
| 2847 | + $this->addOrganizer($exception_props, $recips, true); |
|
| 2848 | + |
|
| 2849 | + // add delegator to meetings |
|
| 2850 | + if ($isDelegate) { |
|
| 2851 | + $this->addDelegator($exception_props, $recips); |
|
| 2852 | + } |
|
| 2853 | + |
|
| 2854 | + $exception_props[$this->proptags['meetingstatus']] = olMeetingReceived; |
|
| 2855 | + $exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded; |
|
| 2856 | + |
|
| 2857 | + if (isset($exception_props[$this->proptags['intendedbusystatus']])) { |
|
| 2858 | + if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) { |
|
| 2859 | + $exception_props[$this->proptags['busystatus']] = fbTentative; |
|
| 2860 | + } |
|
| 2861 | + else { |
|
| 2862 | + $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']]; |
|
| 2863 | + } |
|
| 2864 | + // we already have intendedbusystatus value in $exception_props so no need to copy it |
|
| 2865 | + } |
|
| 2866 | + else { |
|
| 2867 | + $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
|
| 2868 | + } |
|
| 2869 | + |
|
| 2870 | + if ($userAction) { |
|
| 2871 | + $addrInfo = $this->getOwnerAddress($this->store); |
|
| 2872 | + |
|
| 2873 | + // if user has responded then set replytime and name |
|
| 2874 | + $exception_props[$this->proptags['replytime']] = time(); |
|
| 2875 | + if (!empty($addrInfo)) { |
|
| 2876 | + $exception_props[$this->proptags['apptreplyname']] = $addrInfo[0]; |
|
| 2877 | + } |
|
| 2878 | + } |
|
| 2879 | + |
|
| 2880 | + if ($recurr->isException($basedate)) { |
|
| 2881 | + $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem); |
|
| 2882 | + } |
|
| 2883 | + else { |
|
| 2884 | + $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem); |
|
| 2885 | + } |
|
| 2886 | + |
|
| 2887 | + // Move the occurrenceItem to the waste basket |
|
| 2888 | + if ($move) { |
|
| 2889 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 2890 | + $sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]); |
|
| 2891 | + mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 2892 | + } |
|
| 2893 | + |
|
| 2894 | + mapi_savechanges($recurringItem); |
|
| 2895 | + } |
|
| 2896 | + |
|
| 2897 | + /** |
|
| 2898 | + * Function which merges an exception mapi message to recurring message. |
|
| 2899 | + * This will be used when we receive recurring meeting request and we already have an exception message |
|
| 2900 | + * of same meeting in calendar and we need to remove that exception message and add it to attachment table |
|
| 2901 | + * of recurring meeting. |
|
| 2902 | + * |
|
| 2903 | + * @param resource $recurringItem reference to MAPI_message of recurring item |
|
| 2904 | + * @param resource $occurrenceItem reference to MAPI_message of occurrence |
|
| 2905 | + * @param string $basedate basedate of occurrence |
|
| 2906 | + * @param resource $store user store |
|
| 2907 | + */ |
|
| 2908 | + public function mergeException(&$recurringItem, &$occurrenceItem, $basedate, $store) { |
|
| 2909 | + $recurr = new Recurrence($store, $recurringItem); |
|
| 2910 | + |
|
| 2911 | + // Copy properties from meeting request |
|
| 2912 | + $exception_props = mapi_getprops($occurrenceItem); |
|
| 2913 | + |
|
| 2914 | + // Get recipient list from message and add it to exception attachment |
|
| 2915 | + $reciptable = mapi_message_getrecipienttable($occurrenceItem); |
|
| 2916 | + $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
|
| 2917 | + |
|
| 2918 | + if ($recurr->isException($basedate)) { |
|
| 2919 | + $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem); |
|
| 2920 | + } |
|
| 2921 | + else { |
|
| 2922 | + $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem); |
|
| 2923 | + } |
|
| 2924 | + |
|
| 2925 | + // Move the occurrenceItem to the waste basket |
|
| 2926 | + $wastebasket = $this->openDefaultWastebasket($this->openDefaultStore()); |
|
| 2927 | + $sourcefolder = mapi_msgstore_openentry($store, $exception_props[PR_PARENT_ENTRYID]); |
|
| 2928 | + mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
|
| 2929 | + |
|
| 2930 | + mapi_savechanges($recurringItem); |
|
| 2931 | + } |
|
| 2932 | + |
|
| 2933 | + /** |
|
| 2934 | + * Function which submits meeting request based on arguments passed to it. |
|
| 2935 | + * |
|
| 2936 | + * @param resource $message MAPI_message whose meeting request is to be send |
|
| 2937 | + * @param bool $cancel if true send request, else send cancellation |
|
| 2938 | + * @param string $prefix subject prefix |
|
| 2939 | + * @param int $basedate basedate for an occurrence |
|
| 2940 | + * @param object $recurObject recurrence object of mr |
|
| 2941 | + * @param bool $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments |
|
| 2942 | + * @param mixed $modifiedRecips |
|
| 2943 | + * @param mixed $deletedRecips |
|
| 2944 | + */ |
|
| 2945 | + public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $modifiedRecips = false, $deletedRecips = false) { |
|
| 2946 | + $newmessageprops = $messageprops = mapi_getprops($this->message); |
|
| 2947 | + $new = $this->createOutgoingMessage(); |
|
| 2948 | + |
|
| 2949 | + // Copy the entire message into the new meeting request message |
|
| 2950 | + if ($basedate) { |
|
| 2951 | + // messageprops contains properties of whole recurring series |
|
| 2952 | + // and newmessageprops contains properties of exception item |
|
| 2953 | + $newmessageprops = mapi_getprops($message); |
|
| 2954 | + |
|
| 2955 | + // Ensure that the correct basedate is set in the new message |
|
| 2956 | + $newmessageprops[$this->proptags['basedate']] = $basedate; |
|
| 2957 | + |
|
| 2958 | + // Set isRecurring to false, because this is an exception |
|
| 2959 | + $newmessageprops[$this->proptags['recurring']] = false; |
|
| 2960 | + |
|
| 2961 | + // set LID_IS_EXCEPTION to true |
|
| 2962 | + $newmessageprops[$this->proptags['is_exception']] = true; |
|
| 2963 | + |
|
| 2964 | + // Set to high importance |
|
| 2965 | + if ($cancel) { |
|
| 2966 | + $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; |
|
| 2967 | + } |
|
| 2968 | + |
|
| 2969 | + // Set startdate and enddate of exception |
|
| 2970 | + if ($cancel && $recurObject) { |
|
| 2971 | + $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate); |
|
| 2972 | + $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate); |
|
| 2973 | + } |
|
| 2974 | + |
|
| 2975 | + // Set basedate in guid (0x3) |
|
| 2976 | + $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate); |
|
| 2977 | + $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']]; |
|
| 2978 | + $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID]; |
|
| 2979 | + |
|
| 2980 | + // Get deleted recipiets from exception msg |
|
| 2981 | + $restriction = [ |
|
| 2982 | + RES_AND, |
|
| 2983 | + [ |
|
| 2984 | + [ |
|
| 2985 | + RES_BITMASK, |
|
| 2986 | + [ |
|
| 2987 | + ULTYPE => BMR_NEZ, |
|
| 2988 | + ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 2989 | + ULMASK => recipExceptionalDeleted, |
|
| 2990 | + ], |
|
| 2991 | + ], |
|
| 2992 | + [ |
|
| 2993 | + RES_BITMASK, |
|
| 2994 | + [ |
|
| 2995 | + ULTYPE => BMR_EQZ, |
|
| 2996 | + ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 2997 | + ULMASK => recipOrganizer, |
|
| 2998 | + ], |
|
| 2999 | + ], |
|
| 3000 | + ], |
|
| 3001 | + ]; |
|
| 3002 | + |
|
| 3003 | + // In direct-booking mode, we don't need to send cancellations to resources |
|
| 3004 | + if ($this->enableDirectBooking) { |
|
| 3005 | + $restriction[1][] = [ |
|
| 3006 | + RES_PROPERTY, |
|
| 3007 | + [ |
|
| 3008 | + RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3009 | + ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 3010 | + VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 3011 | + ], |
|
| 3012 | + ]; |
|
| 3013 | + } |
|
| 3014 | + |
|
| 3015 | + $recipienttable = mapi_message_getrecipienttable($message); |
|
| 3016 | + $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction); |
|
| 3017 | + |
|
| 3018 | + if (!$deletedRecips) { |
|
| 3019 | + $deletedRecips = array_merge([], $recipients); |
|
| 3020 | + } |
|
| 3021 | + else { |
|
| 3022 | + $deletedRecips = array_merge($deletedRecips, $recipients); |
|
| 3023 | + } |
|
| 3024 | + } |
|
| 3025 | + |
|
| 3026 | + // Remove the PR_ICON_INDEX as it is not needed in the sent message. |
|
| 3027 | + $newmessageprops[PR_ICON_INDEX] = null; |
|
| 3028 | + $newmessageprops[PR_RESPONSE_REQUESTED] = true; |
|
| 3029 | + |
|
| 3030 | + // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar |
|
| 3031 | + $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']]; |
|
| 3032 | + $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']]; |
|
| 3033 | + |
|
| 3034 | + // Set updatecounter/AppointmentSequenceNumber |
|
| 3035 | + // get the value of latest updatecounter for the whole series and use it |
|
| 3036 | + $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']]; |
|
| 3037 | + |
|
| 3038 | + $meetingTimeInfo = $this->getMeetingTimeInfo(); |
|
| 3039 | + |
|
| 3040 | + if ($meetingTimeInfo) { |
|
| 3041 | + $newmessageprops[PR_BODY] = $meetingTimeInfo; |
|
| 3042 | + } |
|
| 3043 | + |
|
| 3044 | + // Send all recurrence info in mail, if this is a recurrence meeting. |
|
| 3045 | + if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) { |
|
| 3046 | + if (!empty($messageprops[$this->proptags['recurring_pattern']])) { |
|
| 3047 | + $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']]; |
|
| 3048 | + } |
|
| 3049 | + $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']]; |
|
| 3050 | + $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']]; |
|
| 3051 | + $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']]; |
|
| 3052 | + |
|
| 3053 | + if ($recurObject) { |
|
| 3054 | + $this->generateRecurDates($recurObject, $messageprops, $newmessageprops); |
|
| 3055 | + } |
|
| 3056 | + } |
|
| 3057 | + |
|
| 3058 | + if (isset($newmessageprops[$this->proptags['counter_proposal']])) { |
|
| 3059 | + unset($newmessageprops[$this->proptags['counter_proposal']]); |
|
| 3060 | + } |
|
| 3061 | + |
|
| 3062 | + // Prefix the subject if needed |
|
| 3063 | + if ($prefix && isset($newmessageprops[PR_SUBJECT])) { |
|
| 3064 | + $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT]; |
|
| 3065 | + } |
|
| 3066 | + |
|
| 3067 | + if (isset($newmessageprops[$this->proptags['categories']]) && |
|
| 3068 | + !empty($newmessageprops[$this->proptags['categories']])) { |
|
| 3069 | + unset($newmessageprops[$this->proptags['categories']]); |
|
| 3070 | + } |
|
| 3071 | + mapi_setprops($new, $newmessageprops); |
|
| 3072 | + |
|
| 3073 | + // Copy attachments |
|
| 3074 | + $this->replaceAttachments($message, $new, $copyExceptions); |
|
| 3075 | + |
|
| 3076 | + // Retrieve only those recipient who should receive this meeting request. |
|
| 3077 | + $stripResourcesRestriction = [ |
|
| 3078 | + RES_AND, |
|
| 3079 | + [ |
|
| 3080 | + [ |
|
| 3081 | + RES_BITMASK, |
|
| 3082 | + [ |
|
| 3083 | + ULTYPE => BMR_EQZ, |
|
| 3084 | + ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 3085 | + ULMASK => recipExceptionalDeleted, |
|
| 3086 | + ], |
|
| 3087 | + ], |
|
| 3088 | + [ |
|
| 3089 | + RES_BITMASK, |
|
| 3090 | + [ |
|
| 3091 | + ULTYPE => BMR_EQZ, |
|
| 3092 | + ULPROPTAG => PR_RECIPIENT_FLAGS, |
|
| 3093 | + ULMASK => recipOrganizer, |
|
| 3094 | + ], |
|
| 3095 | + ], |
|
| 3096 | + ], |
|
| 3097 | + ]; |
|
| 3098 | + |
|
| 3099 | + // In direct-booking mode, resources do not receive a meeting request |
|
| 3100 | + if ($this->enableDirectBooking) { |
|
| 3101 | + $stripResourcesRestriction[1][] = |
|
| 3102 | + [ |
|
| 3103 | + RES_PROPERTY, |
|
| 3104 | + [ |
|
| 3105 | + RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3106 | + ULPROPTAG => PR_RECIPIENT_TYPE, |
|
| 3107 | + VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
|
| 3108 | + ], |
|
| 3109 | + ]; |
|
| 3110 | + } |
|
| 3111 | + |
|
| 3112 | + // If no recipients were explicitly provided, we will send the update to all |
|
| 3113 | + // recipients from the meeting. |
|
| 3114 | + if ($modifiedRecips === false) { |
|
| 3115 | + $recipienttable = mapi_message_getrecipienttable($message); |
|
| 3116 | + $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction); |
|
| 3117 | + |
|
| 3118 | + if ($basedate && empty($modifiedRecips)) { |
|
| 3119 | + // Retrieve full list |
|
| 3120 | + $recipienttable = mapi_message_getrecipienttable($this->message); |
|
| 3121 | + $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops); |
|
| 3122 | + |
|
| 3123 | + // Save recipients in exceptions |
|
| 3124 | + mapi_message_modifyrecipients($message, MODRECIP_ADD, $modifiedRecips); |
|
| 3125 | + |
|
| 3126 | + // Now retrieve only those recipient who should receive this meeting request. |
|
| 3127 | + $modifiedRecips = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction); |
|
| 3128 | + } |
|
| 3129 | + } |
|
| 3130 | + |
|
| 3131 | + // @TODO: handle nonAcceptingResources |
|
| 3132 | + /* |
|
| 3133 | 3133 | * Add resource recipients that did not automatically accept the meeting request. |
| 3134 | 3134 | * (note: meaning that they did not decline the meeting request) |
| 3135 | 3135 | */ /* |
@@ -3137,803 +3137,803 @@ discard block |
||
| 3137 | 3137 | $recipients[] = $this->nonAcceptingResources[$i]; |
| 3138 | 3138 | }*/ |
| 3139 | 3139 | |
| 3140 | - if (!empty($modifiedRecips)) { |
|
| 3141 | - // Strip out the sender/'owner' recipient |
|
| 3142 | - mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips); |
|
| 3143 | - |
|
| 3144 | - // Set some properties that are different in the sent request than |
|
| 3145 | - // in the item in our calendar |
|
| 3146 | - |
|
| 3147 | - // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request |
|
| 3148 | - // should always be fbTentative |
|
| 3149 | - $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']]; |
|
| 3150 | - $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted |
|
| 3151 | - $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet |
|
| 3152 | - $newmessageprops[$this->proptags['attendee_critical_change']] = time(); |
|
| 3153 | - $newmessageprops[$this->proptags['owner_critical_change']] = time(); |
|
| 3154 | - $newmessageprops[$this->proptags['meetingtype']] = mtgRequest; |
|
| 3155 | - |
|
| 3156 | - if ($cancel) { |
|
| 3157 | - $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled'; |
|
| 3158 | - $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request |
|
| 3159 | - $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free |
|
| 3160 | - } |
|
| 3161 | - else { |
|
| 3162 | - $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request'; |
|
| 3163 | - $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request |
|
| 3164 | - } |
|
| 3165 | - |
|
| 3166 | - mapi_setprops($new, $newmessageprops); |
|
| 3167 | - mapi_savechanges($new); |
|
| 3168 | - |
|
| 3169 | - // Submit message to non-resource recipients |
|
| 3170 | - mapi_message_submitmessage($new); |
|
| 3171 | - } |
|
| 3172 | - |
|
| 3173 | - // Search through the deleted recipients, and see if any of them is also |
|
| 3174 | - // listed as a recipient to whom we have send an update. As we don't |
|
| 3175 | - // want to send a cancellation message to recipients who will also receive |
|
| 3176 | - // an meeting update, we have to filter those recipients out. |
|
| 3177 | - if ($deletedRecips) { |
|
| 3178 | - $tmp = []; |
|
| 3179 | - |
|
| 3180 | - foreach ($deletedRecips as $delRecip) { |
|
| 3181 | - $found = false; |
|
| 3182 | - |
|
| 3183 | - // Search if the deleted recipient can be found inside |
|
| 3184 | - // the updated recipients as well. |
|
| 3185 | - foreach ($modifiedRecips as $recip) { |
|
| 3186 | - if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) { |
|
| 3187 | - $found = true; |
|
| 3188 | - |
|
| 3189 | - break; |
|
| 3190 | - } |
|
| 3191 | - } |
|
| 3192 | - |
|
| 3193 | - // If the recipient was not found, it truly is deleted, |
|
| 3194 | - // and we can safely send a cancellation message |
|
| 3195 | - if (!$found) { |
|
| 3196 | - $tmp[] = $delRecip; |
|
| 3197 | - } |
|
| 3198 | - } |
|
| 3199 | - |
|
| 3200 | - $deletedRecips = $tmp; |
|
| 3201 | - } |
|
| 3202 | - |
|
| 3203 | - // Send cancellation to deleted attendees |
|
| 3204 | - if ($deletedRecips && !empty($deletedRecips)) { |
|
| 3205 | - $new = $this->createOutgoingMessage(); |
|
| 3206 | - |
|
| 3207 | - mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips); |
|
| 3208 | - |
|
| 3209 | - $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled'; |
|
| 3210 | - $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request |
|
| 3211 | - $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free |
|
| 3212 | - $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance |
|
| 3213 | - if (isset($newmessageprops[PR_SUBJECT])) { |
|
| 3214 | - $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT]; |
|
| 3215 | - } |
|
| 3216 | - |
|
| 3217 | - mapi_setprops($new, $newmessageprops); |
|
| 3218 | - mapi_savechanges($new); |
|
| 3219 | - |
|
| 3220 | - // Submit message to non-resource recipients |
|
| 3221 | - mapi_message_submitmessage($new); |
|
| 3222 | - } |
|
| 3223 | - |
|
| 3224 | - // Set properties on meeting object in calendar |
|
| 3225 | - // Set requestsent to 'true' (turns on 'tracking', etc) |
|
| 3226 | - $props = []; |
|
| 3227 | - $props[$this->proptags['meetingstatus']] = olMeeting; |
|
| 3228 | - $props[$this->proptags['responsestatus']] = olResponseOrganized; |
|
| 3229 | - // Only set the 'requestsent' property if it wasn't set previously yet, |
|
| 3230 | - // this ensures we will not accidentally set it from true to false. |
|
| 3231 | - if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) { |
|
| 3232 | - $props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource); |
|
| 3233 | - } |
|
| 3234 | - $props[$this->proptags['attendee_critical_change']] = time(); |
|
| 3235 | - $props[$this->proptags['owner_critical_change']] = time(); |
|
| 3236 | - $props[$this->proptags['meetingtype']] = mtgRequest; |
|
| 3237 | - // save the new updatecounter to exception/recurring series/normal meeting |
|
| 3238 | - $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']]; |
|
| 3239 | - |
|
| 3240 | - // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar |
|
| 3241 | - $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']]; |
|
| 3242 | - $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']]; |
|
| 3243 | - |
|
| 3244 | - mapi_setprops($message, $props); |
|
| 3245 | - |
|
| 3246 | - // saving of these properties on calendar item should be handled by caller function |
|
| 3247 | - // based on sending meeting request was successful or not |
|
| 3248 | - } |
|
| 3249 | - |
|
| 3250 | - /** |
|
| 3251 | - * OL2007 uses these 4 properties to specify occurrence that should be updated. |
|
| 3252 | - * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime), |
|
| 3253 | - * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date |
|
| 3254 | - * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and |
|
| 3255 | - * also additionally we are sending these properties. |
|
| 3256 | - * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID. |
|
| 3257 | - * |
|
| 3258 | - * @param object $recurObject instance of recurrence class for this message |
|
| 3259 | - * @param array $messageprops properties of meeting object that is going to be send |
|
| 3260 | - * @param array $newmessageprops properties of meeting request/response that is going to be send |
|
| 3261 | - */ |
|
| 3262 | - public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) { |
|
| 3263 | - if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) { |
|
| 3264 | - $startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']])); |
|
| 3265 | - $endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']])); |
|
| 3266 | - |
|
| 3267 | - $startDate = explode(':', $startDate); |
|
| 3268 | - $endDate = explode(':', $endDate); |
|
| 3269 | - |
|
| 3270 | - // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds |
|
| 3271 | - // RecurStartDate = year * 512 + month_number * 32 + day_number |
|
| 3272 | - $newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]); |
|
| 3273 | - // RecurStartTime = hour * 4096 + minutes * 64 + seconds |
|
| 3274 | - $newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]); |
|
| 3275 | - |
|
| 3276 | - $newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]); |
|
| 3277 | - $newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]); |
|
| 3278 | - } |
|
| 3279 | - } |
|
| 3280 | - |
|
| 3281 | - /** |
|
| 3282 | - * Function will create a new outgoing message that will be used to send meeting mail. |
|
| 3283 | - * |
|
| 3284 | - * @param MAPIStore $store (optional) store that is used when creating response, if delegate is creating outgoing mail |
|
| 3285 | - * then this would point to delegate store |
|
| 3286 | - * |
|
| 3287 | - * @return MAPIMessage outgoing mail that is created and can be used for sending it |
|
| 3288 | - */ |
|
| 3289 | - public function createOutgoingMessage($store = false) { |
|
| 3290 | - // get logged in user's store that will be used to send mail, for delegate this will be |
|
| 3291 | - // delegate store |
|
| 3292 | - $userStore = $this->openDefaultStore(); |
|
| 3293 | - |
|
| 3294 | - $sentprops = []; |
|
| 3295 | - $outbox = $this->openDefaultOutbox($userStore); |
|
| 3296 | - |
|
| 3297 | - $outgoing = mapi_folder_createmessage($outbox); |
|
| 3298 | - |
|
| 3299 | - // check if $store is set and it is not equal to $defaultStore (means its the delegation case) |
|
| 3300 | - if ($store !== false) { |
|
| 3301 | - $storeProps = mapi_getprops($store, [PR_ENTRYID]); |
|
| 3302 | - $userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]); |
|
| 3303 | - |
|
| 3304 | - // @FIXME use entryid comparison functions here |
|
| 3305 | - if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) { |
|
| 3306 | - // get the delegator properties and set it into outgoing mail |
|
| 3307 | - $delegatorDetails = $this->getOwnerAddress($store, false); |
|
| 3308 | - |
|
| 3309 | - if ($delegatorDetails) { |
|
| 3310 | - list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails; |
|
| 3311 | - $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3312 | - $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 3313 | - $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 3314 | - $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 3315 | - $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 3316 | - } |
|
| 3317 | - |
|
| 3318 | - // get the delegate properties and set it into outgoing mail |
|
| 3319 | - $delegateDetails = $this->getOwnerAddress($userStore, false); |
|
| 3320 | - |
|
| 3321 | - if ($delegateDetails) { |
|
| 3322 | - list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails; |
|
| 3323 | - $sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3324 | - $sentprops[PR_SENDER_NAME] = $ownername; |
|
| 3325 | - $sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 3326 | - $sentprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 3327 | - $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 3328 | - } |
|
| 3329 | - } |
|
| 3330 | - } |
|
| 3331 | - else { |
|
| 3332 | - // normal user is sending mail, so both set of properties will be same |
|
| 3333 | - $userDetails = $this->getOwnerAddress($userStore); |
|
| 3334 | - |
|
| 3335 | - if ($userDetails) { |
|
| 3336 | - list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails; |
|
| 3337 | - $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3338 | - $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 3339 | - $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 3340 | - $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 3341 | - $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 3342 | - |
|
| 3343 | - $sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3344 | - $sentprops[PR_SENDER_NAME] = $ownername; |
|
| 3345 | - $sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 3346 | - $sentprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 3347 | - $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 3348 | - } |
|
| 3349 | - } |
|
| 3350 | - |
|
| 3351 | - $sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore); |
|
| 3352 | - |
|
| 3353 | - mapi_setprops($outgoing, $sentprops); |
|
| 3354 | - |
|
| 3355 | - return $outgoing; |
|
| 3356 | - } |
|
| 3357 | - |
|
| 3358 | - /** |
|
| 3359 | - * Function which checks that meeting in attendee's calendar is already updated |
|
| 3360 | - * and we are checking an old meeting request. This function also will update property |
|
| 3361 | - * meetingtype to indicate that its out of date meeting request. |
|
| 3362 | - * |
|
| 3363 | - * @return bool true if meeting request is outofdate else false if it is new |
|
| 3364 | - */ |
|
| 3365 | - public function isMeetingOutOfDate() { |
|
| 3366 | - $result = false; |
|
| 3367 | - |
|
| 3368 | - $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]); |
|
| 3369 | - |
|
| 3370 | - if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) { |
|
| 3371 | - return $result; |
|
| 3372 | - } |
|
| 3373 | - |
|
| 3374 | - if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) { |
|
| 3375 | - return true; |
|
| 3376 | - } |
|
| 3377 | - |
|
| 3378 | - // get the basedate to check for exception |
|
| 3379 | - $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]); |
|
| 3380 | - |
|
| 3381 | - $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 3382 | - |
|
| 3383 | - // if basedate is provided and we could not find the item then it could be that we are checking |
|
| 3384 | - // an exception so get the exception and check it |
|
| 3385 | - if ($basedate && $calendarItem !== false) { |
|
| 3386 | - $exception = $this->getExceptionItem($calendarItem, $basedate); |
|
| 3387 | - |
|
| 3388 | - if ($exception !== false) { |
|
| 3389 | - // we are able to find the exception compare with it |
|
| 3390 | - $calendarItem = $exception; |
|
| 3391 | - } |
|
| 3392 | - // we are not able to find exception, could mean that a significant change has occurred on series |
|
| 3393 | - // and it deleted all exceptions, so compare with series |
|
| 3394 | - // $calendarItem already contains reference to series |
|
| 3395 | - } |
|
| 3396 | - |
|
| 3397 | - if ($calendarItem !== false) { |
|
| 3398 | - $calendarItemProps = mapi_getprops($calendarItem, [ |
|
| 3399 | - $this->proptags['owner_critical_change'], |
|
| 3400 | - $this->proptags['updatecounter'], |
|
| 3401 | - ]); |
|
| 3402 | - |
|
| 3403 | - $updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]); |
|
| 3404 | - |
|
| 3405 | - $criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]); |
|
| 3406 | - |
|
| 3407 | - if ($updateCounter || $criticalChange) { |
|
| 3408 | - // meeting request is out of date, set properties to indicate this |
|
| 3409 | - mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]); |
|
| 3410 | - mapi_savechanges($this->message); |
|
| 3411 | - |
|
| 3412 | - $result = true; |
|
| 3413 | - } |
|
| 3414 | - } |
|
| 3415 | - |
|
| 3416 | - return $result; |
|
| 3417 | - } |
|
| 3418 | - |
|
| 3419 | - /** |
|
| 3420 | - * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar. |
|
| 3421 | - * |
|
| 3422 | - * @param Number $basedate basedate of the exception if we want to compare with exception |
|
| 3423 | - * |
|
| 3424 | - * @return bool true if meeting request is updated later |
|
| 3425 | - */ |
|
| 3426 | - public function isMeetingUpdated($basedate = false) { |
|
| 3427 | - $result = false; |
|
| 3428 | - |
|
| 3429 | - $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]); |
|
| 3430 | - |
|
| 3431 | - if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) { |
|
| 3432 | - return $result; |
|
| 3433 | - } |
|
| 3434 | - |
|
| 3435 | - $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 3436 | - |
|
| 3437 | - if ($calendarItem !== false) { |
|
| 3438 | - // basedate is provided so open exception |
|
| 3439 | - if ($basedate !== false) { |
|
| 3440 | - $exception = $this->getExceptionItem($calendarItem, $basedate); |
|
| 3441 | - |
|
| 3442 | - if ($exception !== false) { |
|
| 3443 | - // we are able to find the exception compare with it |
|
| 3444 | - $calendarItem = $exception; |
|
| 3445 | - } |
|
| 3446 | - // we are not able to find exception, could mean that a significant change has occurred on series |
|
| 3447 | - // and it deleted all exceptions, so compare with series |
|
| 3448 | - // $calendarItem already contains reference to series |
|
| 3449 | - } |
|
| 3450 | - |
|
| 3451 | - if ($calendarItem !== false) { |
|
| 3452 | - $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]); |
|
| 3453 | - |
|
| 3454 | - /* |
|
| 3140 | + if (!empty($modifiedRecips)) { |
|
| 3141 | + // Strip out the sender/'owner' recipient |
|
| 3142 | + mapi_message_modifyrecipients($new, MODRECIP_ADD, $modifiedRecips); |
|
| 3143 | + |
|
| 3144 | + // Set some properties that are different in the sent request than |
|
| 3145 | + // in the item in our calendar |
|
| 3146 | + |
|
| 3147 | + // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request |
|
| 3148 | + // should always be fbTentative |
|
| 3149 | + $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']]; |
|
| 3150 | + $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted |
|
| 3151 | + $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet |
|
| 3152 | + $newmessageprops[$this->proptags['attendee_critical_change']] = time(); |
|
| 3153 | + $newmessageprops[$this->proptags['owner_critical_change']] = time(); |
|
| 3154 | + $newmessageprops[$this->proptags['meetingtype']] = mtgRequest; |
|
| 3155 | + |
|
| 3156 | + if ($cancel) { |
|
| 3157 | + $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled'; |
|
| 3158 | + $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request |
|
| 3159 | + $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free |
|
| 3160 | + } |
|
| 3161 | + else { |
|
| 3162 | + $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request'; |
|
| 3163 | + $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request |
|
| 3164 | + } |
|
| 3165 | + |
|
| 3166 | + mapi_setprops($new, $newmessageprops); |
|
| 3167 | + mapi_savechanges($new); |
|
| 3168 | + |
|
| 3169 | + // Submit message to non-resource recipients |
|
| 3170 | + mapi_message_submitmessage($new); |
|
| 3171 | + } |
|
| 3172 | + |
|
| 3173 | + // Search through the deleted recipients, and see if any of them is also |
|
| 3174 | + // listed as a recipient to whom we have send an update. As we don't |
|
| 3175 | + // want to send a cancellation message to recipients who will also receive |
|
| 3176 | + // an meeting update, we have to filter those recipients out. |
|
| 3177 | + if ($deletedRecips) { |
|
| 3178 | + $tmp = []; |
|
| 3179 | + |
|
| 3180 | + foreach ($deletedRecips as $delRecip) { |
|
| 3181 | + $found = false; |
|
| 3182 | + |
|
| 3183 | + // Search if the deleted recipient can be found inside |
|
| 3184 | + // the updated recipients as well. |
|
| 3185 | + foreach ($modifiedRecips as $recip) { |
|
| 3186 | + if ($this->compareABEntryIDs($recip[PR_ENTRYID], $delRecip[PR_ENTRYID])) { |
|
| 3187 | + $found = true; |
|
| 3188 | + |
|
| 3189 | + break; |
|
| 3190 | + } |
|
| 3191 | + } |
|
| 3192 | + |
|
| 3193 | + // If the recipient was not found, it truly is deleted, |
|
| 3194 | + // and we can safely send a cancellation message |
|
| 3195 | + if (!$found) { |
|
| 3196 | + $tmp[] = $delRecip; |
|
| 3197 | + } |
|
| 3198 | + } |
|
| 3199 | + |
|
| 3200 | + $deletedRecips = $tmp; |
|
| 3201 | + } |
|
| 3202 | + |
|
| 3203 | + // Send cancellation to deleted attendees |
|
| 3204 | + if ($deletedRecips && !empty($deletedRecips)) { |
|
| 3205 | + $new = $this->createOutgoingMessage(); |
|
| 3206 | + |
|
| 3207 | + mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips); |
|
| 3208 | + |
|
| 3209 | + $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled'; |
|
| 3210 | + $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request |
|
| 3211 | + $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free |
|
| 3212 | + $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance |
|
| 3213 | + if (isset($newmessageprops[PR_SUBJECT])) { |
|
| 3214 | + $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT]; |
|
| 3215 | + } |
|
| 3216 | + |
|
| 3217 | + mapi_setprops($new, $newmessageprops); |
|
| 3218 | + mapi_savechanges($new); |
|
| 3219 | + |
|
| 3220 | + // Submit message to non-resource recipients |
|
| 3221 | + mapi_message_submitmessage($new); |
|
| 3222 | + } |
|
| 3223 | + |
|
| 3224 | + // Set properties on meeting object in calendar |
|
| 3225 | + // Set requestsent to 'true' (turns on 'tracking', etc) |
|
| 3226 | + $props = []; |
|
| 3227 | + $props[$this->proptags['meetingstatus']] = olMeeting; |
|
| 3228 | + $props[$this->proptags['responsestatus']] = olResponseOrganized; |
|
| 3229 | + // Only set the 'requestsent' property if it wasn't set previously yet, |
|
| 3230 | + // this ensures we will not accidentally set it from true to false. |
|
| 3231 | + if (!isset($messageprops[$this->proptags['requestsent']]) || $messageprops[$this->proptags['requestsent']] !== true) { |
|
| 3232 | + $props[$this->proptags['requestsent']] = !empty($modifiedRecips) || ($this->includesResources && !$this->errorSetResource); |
|
| 3233 | + } |
|
| 3234 | + $props[$this->proptags['attendee_critical_change']] = time(); |
|
| 3235 | + $props[$this->proptags['owner_critical_change']] = time(); |
|
| 3236 | + $props[$this->proptags['meetingtype']] = mtgRequest; |
|
| 3237 | + // save the new updatecounter to exception/recurring series/normal meeting |
|
| 3238 | + $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']]; |
|
| 3239 | + |
|
| 3240 | + // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar |
|
| 3241 | + $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']]; |
|
| 3242 | + $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']]; |
|
| 3243 | + |
|
| 3244 | + mapi_setprops($message, $props); |
|
| 3245 | + |
|
| 3246 | + // saving of these properties on calendar item should be handled by caller function |
|
| 3247 | + // based on sending meeting request was successful or not |
|
| 3248 | + } |
|
| 3249 | + |
|
| 3250 | + /** |
|
| 3251 | + * OL2007 uses these 4 properties to specify occurrence that should be updated. |
|
| 3252 | + * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime), |
|
| 3253 | + * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date |
|
| 3254 | + * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and |
|
| 3255 | + * also additionally we are sending these properties. |
|
| 3256 | + * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID. |
|
| 3257 | + * |
|
| 3258 | + * @param object $recurObject instance of recurrence class for this message |
|
| 3259 | + * @param array $messageprops properties of meeting object that is going to be send |
|
| 3260 | + * @param array $newmessageprops properties of meeting request/response that is going to be send |
|
| 3261 | + */ |
|
| 3262 | + public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) { |
|
| 3263 | + if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) { |
|
| 3264 | + $startDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']])); |
|
| 3265 | + $endDate = date('Y:n:j:G:i:s', $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']])); |
|
| 3266 | + |
|
| 3267 | + $startDate = explode(':', $startDate); |
|
| 3268 | + $endDate = explode(':', $endDate); |
|
| 3269 | + |
|
| 3270 | + // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds |
|
| 3271 | + // RecurStartDate = year * 512 + month_number * 32 + day_number |
|
| 3272 | + $newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]); |
|
| 3273 | + // RecurStartTime = hour * 4096 + minutes * 64 + seconds |
|
| 3274 | + $newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]); |
|
| 3275 | + |
|
| 3276 | + $newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]); |
|
| 3277 | + $newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]); |
|
| 3278 | + } |
|
| 3279 | + } |
|
| 3280 | + |
|
| 3281 | + /** |
|
| 3282 | + * Function will create a new outgoing message that will be used to send meeting mail. |
|
| 3283 | + * |
|
| 3284 | + * @param MAPIStore $store (optional) store that is used when creating response, if delegate is creating outgoing mail |
|
| 3285 | + * then this would point to delegate store |
|
| 3286 | + * |
|
| 3287 | + * @return MAPIMessage outgoing mail that is created and can be used for sending it |
|
| 3288 | + */ |
|
| 3289 | + public function createOutgoingMessage($store = false) { |
|
| 3290 | + // get logged in user's store that will be used to send mail, for delegate this will be |
|
| 3291 | + // delegate store |
|
| 3292 | + $userStore = $this->openDefaultStore(); |
|
| 3293 | + |
|
| 3294 | + $sentprops = []; |
|
| 3295 | + $outbox = $this->openDefaultOutbox($userStore); |
|
| 3296 | + |
|
| 3297 | + $outgoing = mapi_folder_createmessage($outbox); |
|
| 3298 | + |
|
| 3299 | + // check if $store is set and it is not equal to $defaultStore (means its the delegation case) |
|
| 3300 | + if ($store !== false) { |
|
| 3301 | + $storeProps = mapi_getprops($store, [PR_ENTRYID]); |
|
| 3302 | + $userStoreProps = mapi_getprops($userStore, [PR_ENTRYID]); |
|
| 3303 | + |
|
| 3304 | + // @FIXME use entryid comparison functions here |
|
| 3305 | + if ($storeProps[PR_ENTRYID] !== $userStoreProps[PR_ENTRYID]) { |
|
| 3306 | + // get the delegator properties and set it into outgoing mail |
|
| 3307 | + $delegatorDetails = $this->getOwnerAddress($store, false); |
|
| 3308 | + |
|
| 3309 | + if ($delegatorDetails) { |
|
| 3310 | + list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegatorDetails; |
|
| 3311 | + $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3312 | + $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 3313 | + $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 3314 | + $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 3315 | + $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 3316 | + } |
|
| 3317 | + |
|
| 3318 | + // get the delegate properties and set it into outgoing mail |
|
| 3319 | + $delegateDetails = $this->getOwnerAddress($userStore, false); |
|
| 3320 | + |
|
| 3321 | + if ($delegateDetails) { |
|
| 3322 | + list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $delegateDetails; |
|
| 3323 | + $sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3324 | + $sentprops[PR_SENDER_NAME] = $ownername; |
|
| 3325 | + $sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 3326 | + $sentprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 3327 | + $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 3328 | + } |
|
| 3329 | + } |
|
| 3330 | + } |
|
| 3331 | + else { |
|
| 3332 | + // normal user is sending mail, so both set of properties will be same |
|
| 3333 | + $userDetails = $this->getOwnerAddress($userStore); |
|
| 3334 | + |
|
| 3335 | + if ($userDetails) { |
|
| 3336 | + list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $userDetails; |
|
| 3337 | + $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3338 | + $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername; |
|
| 3339 | + $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype; |
|
| 3340 | + $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
|
| 3341 | + $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
|
| 3342 | + |
|
| 3343 | + $sentprops[PR_SENDER_EMAIL_ADDRESS] = $owneremailaddr; |
|
| 3344 | + $sentprops[PR_SENDER_NAME] = $ownername; |
|
| 3345 | + $sentprops[PR_SENDER_ADDRTYPE] = $owneraddrtype; |
|
| 3346 | + $sentprops[PR_SENDER_ENTRYID] = $ownerentryid; |
|
| 3347 | + $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
|
| 3348 | + } |
|
| 3349 | + } |
|
| 3350 | + |
|
| 3351 | + $sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($userStore); |
|
| 3352 | + |
|
| 3353 | + mapi_setprops($outgoing, $sentprops); |
|
| 3354 | + |
|
| 3355 | + return $outgoing; |
|
| 3356 | + } |
|
| 3357 | + |
|
| 3358 | + /** |
|
| 3359 | + * Function which checks that meeting in attendee's calendar is already updated |
|
| 3360 | + * and we are checking an old meeting request. This function also will update property |
|
| 3361 | + * meetingtype to indicate that its out of date meeting request. |
|
| 3362 | + * |
|
| 3363 | + * @return bool true if meeting request is outofdate else false if it is new |
|
| 3364 | + */ |
|
| 3365 | + public function isMeetingOutOfDate() { |
|
| 3366 | + $result = false; |
|
| 3367 | + |
|
| 3368 | + $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]); |
|
| 3369 | + |
|
| 3370 | + if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS])) { |
|
| 3371 | + return $result; |
|
| 3372 | + } |
|
| 3373 | + |
|
| 3374 | + if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) { |
|
| 3375 | + return true; |
|
| 3376 | + } |
|
| 3377 | + |
|
| 3378 | + // get the basedate to check for exception |
|
| 3379 | + $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]); |
|
| 3380 | + |
|
| 3381 | + $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 3382 | + |
|
| 3383 | + // if basedate is provided and we could not find the item then it could be that we are checking |
|
| 3384 | + // an exception so get the exception and check it |
|
| 3385 | + if ($basedate && $calendarItem !== false) { |
|
| 3386 | + $exception = $this->getExceptionItem($calendarItem, $basedate); |
|
| 3387 | + |
|
| 3388 | + if ($exception !== false) { |
|
| 3389 | + // we are able to find the exception compare with it |
|
| 3390 | + $calendarItem = $exception; |
|
| 3391 | + } |
|
| 3392 | + // we are not able to find exception, could mean that a significant change has occurred on series |
|
| 3393 | + // and it deleted all exceptions, so compare with series |
|
| 3394 | + // $calendarItem already contains reference to series |
|
| 3395 | + } |
|
| 3396 | + |
|
| 3397 | + if ($calendarItem !== false) { |
|
| 3398 | + $calendarItemProps = mapi_getprops($calendarItem, [ |
|
| 3399 | + $this->proptags['owner_critical_change'], |
|
| 3400 | + $this->proptags['updatecounter'], |
|
| 3401 | + ]); |
|
| 3402 | + |
|
| 3403 | + $updateCounter = (isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]); |
|
| 3404 | + |
|
| 3405 | + $criticalChange = (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']]); |
|
| 3406 | + |
|
| 3407 | + if ($updateCounter || $criticalChange) { |
|
| 3408 | + // meeting request is out of date, set properties to indicate this |
|
| 3409 | + mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]); |
|
| 3410 | + mapi_savechanges($this->message); |
|
| 3411 | + |
|
| 3412 | + $result = true; |
|
| 3413 | + } |
|
| 3414 | + } |
|
| 3415 | + |
|
| 3416 | + return $result; |
|
| 3417 | + } |
|
| 3418 | + |
|
| 3419 | + /** |
|
| 3420 | + * Function which checks that if we have received a meeting response for an updated meeting in organizer's calendar. |
|
| 3421 | + * |
|
| 3422 | + * @param Number $basedate basedate of the exception if we want to compare with exception |
|
| 3423 | + * |
|
| 3424 | + * @return bool true if meeting request is updated later |
|
| 3425 | + */ |
|
| 3426 | + public function isMeetingUpdated($basedate = false) { |
|
| 3427 | + $result = false; |
|
| 3428 | + |
|
| 3429 | + $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['updatecounter']]); |
|
| 3430 | + |
|
| 3431 | + if (!$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS])) { |
|
| 3432 | + return $result; |
|
| 3433 | + } |
|
| 3434 | + |
|
| 3435 | + $calendarItem = $this->getCorrespondentCalendarItem(true); |
|
| 3436 | + |
|
| 3437 | + if ($calendarItem !== false) { |
|
| 3438 | + // basedate is provided so open exception |
|
| 3439 | + if ($basedate !== false) { |
|
| 3440 | + $exception = $this->getExceptionItem($calendarItem, $basedate); |
|
| 3441 | + |
|
| 3442 | + if ($exception !== false) { |
|
| 3443 | + // we are able to find the exception compare with it |
|
| 3444 | + $calendarItem = $exception; |
|
| 3445 | + } |
|
| 3446 | + // we are not able to find exception, could mean that a significant change has occurred on series |
|
| 3447 | + // and it deleted all exceptions, so compare with series |
|
| 3448 | + // $calendarItem already contains reference to series |
|
| 3449 | + } |
|
| 3450 | + |
|
| 3451 | + if ($calendarItem !== false) { |
|
| 3452 | + $calendarItemProps = mapi_getprops($calendarItem, [$this->proptags['updatecounter']]); |
|
| 3453 | + |
|
| 3454 | + /* |
|
| 3455 | 3455 | * if(message_counter < appointment_counter) meeting object is newer then meeting response (meeting is updated) |
| 3456 | 3456 | * if(message_counter >= appointment_counter) meeting is not updated, do normal processing |
| 3457 | 3457 | */ |
| 3458 | - if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) { |
|
| 3459 | - if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) { |
|
| 3460 | - $result = true; |
|
| 3461 | - } |
|
| 3462 | - } |
|
| 3463 | - } |
|
| 3464 | - } |
|
| 3465 | - |
|
| 3466 | - return $result; |
|
| 3467 | - } |
|
| 3468 | - |
|
| 3469 | - /** |
|
| 3470 | - * Checks if there has been any significant changes on appointment/meeting item. |
|
| 3471 | - * Significant changes be: |
|
| 3472 | - * 1) startdate has been changed |
|
| 3473 | - * 2) duedate has been changed OR |
|
| 3474 | - * 3) recurrence pattern has been created, modified or removed. |
|
| 3475 | - * |
|
| 3476 | - * @param array oldProps old props before an update |
|
| 3477 | - * @param Number basedate basedate |
|
| 3478 | - * @param bool isRecurrenceChanged for change in recurrence pattern. |
|
| 3479 | - * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response |
|
| 3480 | - * @param mixed $oldProps |
|
| 3481 | - * @param mixed $basedate |
|
| 3482 | - * @param mixed $isRecurrenceChanged |
|
| 3483 | - */ |
|
| 3484 | - public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) { |
|
| 3485 | - $message = null; |
|
| 3486 | - $attach = null; |
|
| 3487 | - |
|
| 3488 | - // If basedate is specified then we need to open exception message to clear recipient responses |
|
| 3489 | - if ($basedate) { |
|
| 3490 | - $recurrence = new Recurrence($this->store, $this->message); |
|
| 3491 | - if ($recurrence->isException($basedate)) { |
|
| 3492 | - $attach = $recurrence->getExceptionAttachment($basedate); |
|
| 3493 | - if ($attach) { |
|
| 3494 | - $message = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 3495 | - } |
|
| 3496 | - } |
|
| 3497 | - } |
|
| 3498 | - else { |
|
| 3499 | - // use normal message or recurring series message |
|
| 3500 | - $message = $this->message; |
|
| 3501 | - } |
|
| 3502 | - |
|
| 3503 | - if (!$message) { |
|
| 3504 | - return; |
|
| 3505 | - } |
|
| 3506 | - |
|
| 3507 | - $newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]); |
|
| 3508 | - |
|
| 3509 | - // Check whether message is updated or not. |
|
| 3510 | - if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) { |
|
| 3511 | - return; |
|
| 3512 | - } |
|
| 3513 | - |
|
| 3514 | - if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) || |
|
| 3515 | - ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) || |
|
| 3516 | - $isRecurrenceChanged) { |
|
| 3517 | - $this->clearRecipientResponse($message); |
|
| 3518 | - |
|
| 3519 | - mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]); |
|
| 3520 | - |
|
| 3521 | - mapi_savechanges($message); |
|
| 3522 | - if ($attach) { // Also save attachment Object. |
|
| 3523 | - mapi_savechanges($attach); |
|
| 3524 | - } |
|
| 3525 | - } |
|
| 3526 | - } |
|
| 3527 | - |
|
| 3528 | - /** |
|
| 3529 | - * Clear responses of all attendees who have replied in past. |
|
| 3530 | - * |
|
| 3531 | - * @param MAPI_MESSAGE $message on which responses should be cleared |
|
| 3532 | - */ |
|
| 3533 | - public function clearRecipientResponse($message) { |
|
| 3534 | - $recipTable = mapi_message_getrecipienttable($message); |
|
| 3535 | - $recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops); |
|
| 3536 | - |
|
| 3537 | - foreach ($recipsRows as $recipient) { |
|
| 3538 | - if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) { |
|
| 3539 | - // Recipient is attendee, set the trackstatus to 'Not Responded' |
|
| 3540 | - $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 3541 | - } |
|
| 3542 | - else { |
|
| 3543 | - // Recipient is organizer, this is not possible, but for safety |
|
| 3544 | - // it is best to clear the trackstatus for him as well by setting |
|
| 3545 | - // the trackstatus to 'Organized'. |
|
| 3546 | - $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 3547 | - } |
|
| 3548 | - mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]); |
|
| 3549 | - } |
|
| 3550 | - } |
|
| 3551 | - |
|
| 3552 | - /** |
|
| 3553 | - * Function returns correspondent calendar item attached with the meeting request/response/cancellation. |
|
| 3554 | - * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is |
|
| 3555 | - * for exception then this function will return recurring series for that meeting request |
|
| 3556 | - * after that you need to use getExceptionItem function to get exception item that will be |
|
| 3557 | - * fetched from the attachment table of recurring series MAPIMessage. |
|
| 3558 | - * |
|
| 3559 | - * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true. |
|
| 3560 | - * |
|
| 3561 | - * @return entryid or MAPIMessage resource of calendar item |
|
| 3562 | - */ |
|
| 3563 | - public function getCorrespondentCalendarItem($open = true) { |
|
| 3564 | - $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 3565 | - |
|
| 3566 | - if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) { |
|
| 3567 | - // can work only with meeting requests/responses/cancellations |
|
| 3568 | - return false; |
|
| 3569 | - } |
|
| 3570 | - |
|
| 3571 | - $globalId = $props[$this->proptags['goid']]; |
|
| 3572 | - $cleanGlobalId = $props[$this->proptags['goid2']]; |
|
| 3573 | - |
|
| 3574 | - // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar. |
|
| 3575 | - if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 3576 | - $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 3577 | - |
|
| 3578 | - $store = $delegatorStore['store']; |
|
| 3579 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 3580 | - } |
|
| 3581 | - else { |
|
| 3582 | - $store = $this->store; |
|
| 3583 | - $calFolder = $this->openDefaultCalendar(); |
|
| 3584 | - } |
|
| 3585 | - |
|
| 3586 | - $basedate = $this->getBasedateFromGlobalID($globalId); |
|
| 3587 | - |
|
| 3588 | - /** |
|
| 3589 | - * First search for any appointments which correspond to the $globalId, |
|
| 3590 | - * this can be the entire series (if the Meeting Request refers to the |
|
| 3591 | - * entire series), or an particular Occurrence (if the meeting Request |
|
| 3592 | - * contains a basedate). |
|
| 3593 | - * |
|
| 3594 | - * If we cannot find a corresponding item, and the $globalId contains |
|
| 3595 | - * a $basedate, it might imply that a new exception will have to be |
|
| 3596 | - * created for a series which is present in the calendar, we can look |
|
| 3597 | - * that one up by searching for the $cleanGlobalId. |
|
| 3598 | - */ |
|
| 3599 | - $entryids = $this->findCalendarItems($globalId, $calFolder); |
|
| 3600 | - if ($basedate && empty($entryids)) { |
|
| 3601 | - $entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true); |
|
| 3602 | - } |
|
| 3603 | - |
|
| 3604 | - // there should be only one item returned |
|
| 3605 | - if (!empty($entryids) && count($entryids) === 1) { |
|
| 3606 | - // return only entryid |
|
| 3607 | - if ($open === false) { |
|
| 3608 | - return $entryids[0]; |
|
| 3609 | - } |
|
| 3610 | - |
|
| 3611 | - // open calendar item and return it |
|
| 3612 | - return mapi_msgstore_openentry($store, $entryids[0]); |
|
| 3613 | - } |
|
| 3614 | - |
|
| 3615 | - // no items found in calendar |
|
| 3616 | - return false; |
|
| 3617 | - } |
|
| 3618 | - |
|
| 3619 | - /** |
|
| 3620 | - * Function returns exception item based on the basedate passed. |
|
| 3621 | - * |
|
| 3622 | - * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar |
|
| 3623 | - * @param Unixtime $basedate basedate of exception that needs to be returned |
|
| 3624 | - * @param MAPIStore $store store that contains the recurring calendar item |
|
| 3625 | - * |
|
| 3626 | - * @return entryid or MAPIMessage resource of exception item |
|
| 3627 | - */ |
|
| 3628 | - public function getExceptionItem($recurringMessage, $basedate, $store = false) { |
|
| 3629 | - $occurItem = false; |
|
| 3630 | - |
|
| 3631 | - $props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]); |
|
| 3632 | - |
|
| 3633 | - // check if the passed item is recurring series |
|
| 3634 | - if ($props[$this->proptags['recurring']] !== false) { |
|
| 3635 | - return false; |
|
| 3636 | - } |
|
| 3637 | - |
|
| 3638 | - if ($store === false) { |
|
| 3639 | - // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar. |
|
| 3640 | - if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 3641 | - $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 3642 | - $store = $delegatorStore['store']; |
|
| 3643 | - } |
|
| 3644 | - else { |
|
| 3645 | - $store = $this->store; |
|
| 3646 | - } |
|
| 3647 | - } |
|
| 3648 | - |
|
| 3649 | - $recurr = new Recurrence($store, $recurringMessage); |
|
| 3650 | - $attach = $recurr->getExceptionAttachment($basedate); |
|
| 3651 | - if ($attach) { |
|
| 3652 | - $occurItem = mapi_attach_openobj($attach); |
|
| 3653 | - } |
|
| 3654 | - |
|
| 3655 | - return $occurItem; |
|
| 3656 | - } |
|
| 3657 | - |
|
| 3658 | - /** |
|
| 3659 | - * Function which checks whether received meeting request is either conflicting with other appointments or not. |
|
| 3660 | - * |
|
| 3661 | - * @param MAPIMessage $message meeting request item that should be checked for conflicts in calendar |
|
| 3662 | - * @param MAPIStore $userStore store containing calendar folder that will be used for confilict checking |
|
| 3663 | - * @param MAPIFolder $calFolder calendar folder for conflict checking |
|
| 3664 | - * |
|
| 3665 | - * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances |
|
| 3666 | - * conflict of recurring meeting and false if meeting is not conflicting |
|
| 3667 | - * @return mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message |
|
| 3668 | - */ |
|
| 3669 | - public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) { |
|
| 3670 | - $returnValue = false; |
|
| 3671 | - $noOfInstances = 0; |
|
| 3672 | - |
|
| 3673 | - if ($message === false) { |
|
| 3674 | - $message = $this->message; |
|
| 3675 | - } |
|
| 3676 | - |
|
| 3677 | - $messageProps = mapi_getprops( |
|
| 3678 | - $message, |
|
| 3679 | - [ |
|
| 3680 | - PR_MESSAGE_CLASS, |
|
| 3681 | - $this->proptags['goid'], |
|
| 3682 | - $this->proptags['goid2'], |
|
| 3683 | - $this->proptags['startdate'], |
|
| 3684 | - $this->proptags['duedate'], |
|
| 3685 | - $this->proptags['recurring'], |
|
| 3686 | - $this->proptags['clipstart'], |
|
| 3687 | - $this->proptags['clipend'], |
|
| 3688 | - PR_RCVD_REPRESENTING_ENTRYID, |
|
| 3689 | - $this->proptags['basedate'], |
|
| 3690 | - PR_RCVD_REPRESENTING_NAME, |
|
| 3691 | - ] |
|
| 3692 | - ); |
|
| 3693 | - |
|
| 3694 | - if ($userStore === false) { |
|
| 3695 | - $userStore = $this->store; |
|
| 3696 | - |
|
| 3697 | - // check if delegate is processing the response |
|
| 3698 | - if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 3699 | - $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 3700 | - |
|
| 3701 | - $userStore = $delegatorStore['store']; |
|
| 3702 | - $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 3703 | - } |
|
| 3704 | - } |
|
| 3705 | - |
|
| 3706 | - if ($calFolder === false) { |
|
| 3707 | - $calFolder = $this->openDefaultCalendar($userStore); |
|
| 3708 | - } |
|
| 3709 | - |
|
| 3710 | - if ($calFolder) { |
|
| 3711 | - // Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar. |
|
| 3712 | - if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) { |
|
| 3713 | - // Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date') |
|
| 3714 | - $recurr = new Recurrence($userStore, $message); |
|
| 3715 | - $items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30); |
|
| 3716 | - |
|
| 3717 | - foreach ($items as $item) { |
|
| 3718 | - // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item |
|
| 3719 | - $calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]); |
|
| 3720 | - |
|
| 3721 | - foreach ($calendarItems as $calendarItem) { |
|
| 3722 | - if ($calendarItem[$this->proptags['busystatus']] !== fbFree) { |
|
| 3723 | - /* |
|
| 3458 | + if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']])) { |
|
| 3459 | + if ($props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) { |
|
| 3460 | + $result = true; |
|
| 3461 | + } |
|
| 3462 | + } |
|
| 3463 | + } |
|
| 3464 | + } |
|
| 3465 | + |
|
| 3466 | + return $result; |
|
| 3467 | + } |
|
| 3468 | + |
|
| 3469 | + /** |
|
| 3470 | + * Checks if there has been any significant changes on appointment/meeting item. |
|
| 3471 | + * Significant changes be: |
|
| 3472 | + * 1) startdate has been changed |
|
| 3473 | + * 2) duedate has been changed OR |
|
| 3474 | + * 3) recurrence pattern has been created, modified or removed. |
|
| 3475 | + * |
|
| 3476 | + * @param array oldProps old props before an update |
|
| 3477 | + * @param Number basedate basedate |
|
| 3478 | + * @param bool isRecurrenceChanged for change in recurrence pattern. |
|
| 3479 | + * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response |
|
| 3480 | + * @param mixed $oldProps |
|
| 3481 | + * @param mixed $basedate |
|
| 3482 | + * @param mixed $isRecurrenceChanged |
|
| 3483 | + */ |
|
| 3484 | + public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) { |
|
| 3485 | + $message = null; |
|
| 3486 | + $attach = null; |
|
| 3487 | + |
|
| 3488 | + // If basedate is specified then we need to open exception message to clear recipient responses |
|
| 3489 | + if ($basedate) { |
|
| 3490 | + $recurrence = new Recurrence($this->store, $this->message); |
|
| 3491 | + if ($recurrence->isException($basedate)) { |
|
| 3492 | + $attach = $recurrence->getExceptionAttachment($basedate); |
|
| 3493 | + if ($attach) { |
|
| 3494 | + $message = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 3495 | + } |
|
| 3496 | + } |
|
| 3497 | + } |
|
| 3498 | + else { |
|
| 3499 | + // use normal message or recurring series message |
|
| 3500 | + $message = $this->message; |
|
| 3501 | + } |
|
| 3502 | + |
|
| 3503 | + if (!$message) { |
|
| 3504 | + return; |
|
| 3505 | + } |
|
| 3506 | + |
|
| 3507 | + $newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]); |
|
| 3508 | + |
|
| 3509 | + // Check whether message is updated or not. |
|
| 3510 | + if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) { |
|
| 3511 | + return; |
|
| 3512 | + } |
|
| 3513 | + |
|
| 3514 | + if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) || |
|
| 3515 | + ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) || |
|
| 3516 | + $isRecurrenceChanged) { |
|
| 3517 | + $this->clearRecipientResponse($message); |
|
| 3518 | + |
|
| 3519 | + mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]); |
|
| 3520 | + |
|
| 3521 | + mapi_savechanges($message); |
|
| 3522 | + if ($attach) { // Also save attachment Object. |
|
| 3523 | + mapi_savechanges($attach); |
|
| 3524 | + } |
|
| 3525 | + } |
|
| 3526 | + } |
|
| 3527 | + |
|
| 3528 | + /** |
|
| 3529 | + * Clear responses of all attendees who have replied in past. |
|
| 3530 | + * |
|
| 3531 | + * @param MAPI_MESSAGE $message on which responses should be cleared |
|
| 3532 | + */ |
|
| 3533 | + public function clearRecipientResponse($message) { |
|
| 3534 | + $recipTable = mapi_message_getrecipienttable($message); |
|
| 3535 | + $recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops); |
|
| 3536 | + |
|
| 3537 | + foreach ($recipsRows as $recipient) { |
|
| 3538 | + if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) { |
|
| 3539 | + // Recipient is attendee, set the trackstatus to 'Not Responded' |
|
| 3540 | + $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 3541 | + } |
|
| 3542 | + else { |
|
| 3543 | + // Recipient is organizer, this is not possible, but for safety |
|
| 3544 | + // it is best to clear the trackstatus for him as well by setting |
|
| 3545 | + // the trackstatus to 'Organized'. |
|
| 3546 | + $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 3547 | + } |
|
| 3548 | + mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]); |
|
| 3549 | + } |
|
| 3550 | + } |
|
| 3551 | + |
|
| 3552 | + /** |
|
| 3553 | + * Function returns correspondent calendar item attached with the meeting request/response/cancellation. |
|
| 3554 | + * This will only check for actual MAPIMessages in calendar folder, so if a meeting request is |
|
| 3555 | + * for exception then this function will return recurring series for that meeting request |
|
| 3556 | + * after that you need to use getExceptionItem function to get exception item that will be |
|
| 3557 | + * fetched from the attachment table of recurring series MAPIMessage. |
|
| 3558 | + * |
|
| 3559 | + * @param bool $open boolean to indicate the function should return entryid or MAPIMessage. Defaults to true. |
|
| 3560 | + * |
|
| 3561 | + * @return entryid or MAPIMessage resource of calendar item |
|
| 3562 | + */ |
|
| 3563 | + public function getCorrespondentCalendarItem($open = true) { |
|
| 3564 | + $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 3565 | + |
|
| 3566 | + if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) { |
|
| 3567 | + // can work only with meeting requests/responses/cancellations |
|
| 3568 | + return false; |
|
| 3569 | + } |
|
| 3570 | + |
|
| 3571 | + $globalId = $props[$this->proptags['goid']]; |
|
| 3572 | + $cleanGlobalId = $props[$this->proptags['goid2']]; |
|
| 3573 | + |
|
| 3574 | + // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar. |
|
| 3575 | + if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 3576 | + $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 3577 | + |
|
| 3578 | + $store = $delegatorStore['store']; |
|
| 3579 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 3580 | + } |
|
| 3581 | + else { |
|
| 3582 | + $store = $this->store; |
|
| 3583 | + $calFolder = $this->openDefaultCalendar(); |
|
| 3584 | + } |
|
| 3585 | + |
|
| 3586 | + $basedate = $this->getBasedateFromGlobalID($globalId); |
|
| 3587 | + |
|
| 3588 | + /** |
|
| 3589 | + * First search for any appointments which correspond to the $globalId, |
|
| 3590 | + * this can be the entire series (if the Meeting Request refers to the |
|
| 3591 | + * entire series), or an particular Occurrence (if the meeting Request |
|
| 3592 | + * contains a basedate). |
|
| 3593 | + * |
|
| 3594 | + * If we cannot find a corresponding item, and the $globalId contains |
|
| 3595 | + * a $basedate, it might imply that a new exception will have to be |
|
| 3596 | + * created for a series which is present in the calendar, we can look |
|
| 3597 | + * that one up by searching for the $cleanGlobalId. |
|
| 3598 | + */ |
|
| 3599 | + $entryids = $this->findCalendarItems($globalId, $calFolder); |
|
| 3600 | + if ($basedate && empty($entryids)) { |
|
| 3601 | + $entryids = $this->findCalendarItems($cleanGlobalId, $calFolder, true); |
|
| 3602 | + } |
|
| 3603 | + |
|
| 3604 | + // there should be only one item returned |
|
| 3605 | + if (!empty($entryids) && count($entryids) === 1) { |
|
| 3606 | + // return only entryid |
|
| 3607 | + if ($open === false) { |
|
| 3608 | + return $entryids[0]; |
|
| 3609 | + } |
|
| 3610 | + |
|
| 3611 | + // open calendar item and return it |
|
| 3612 | + return mapi_msgstore_openentry($store, $entryids[0]); |
|
| 3613 | + } |
|
| 3614 | + |
|
| 3615 | + // no items found in calendar |
|
| 3616 | + return false; |
|
| 3617 | + } |
|
| 3618 | + |
|
| 3619 | + /** |
|
| 3620 | + * Function returns exception item based on the basedate passed. |
|
| 3621 | + * |
|
| 3622 | + * @param MAPIMessage $recurringMessage Resource of Recurring meeting from calendar |
|
| 3623 | + * @param Unixtime $basedate basedate of exception that needs to be returned |
|
| 3624 | + * @param MAPIStore $store store that contains the recurring calendar item |
|
| 3625 | + * |
|
| 3626 | + * @return entryid or MAPIMessage resource of exception item |
|
| 3627 | + */ |
|
| 3628 | + public function getExceptionItem($recurringMessage, $basedate, $store = false) { |
|
| 3629 | + $occurItem = false; |
|
| 3630 | + |
|
| 3631 | + $props = mapi_getprops($this->message, [PR_RCVD_REPRESENTING_ENTRYID, $this->proptags['recurring']]); |
|
| 3632 | + |
|
| 3633 | + // check if the passed item is recurring series |
|
| 3634 | + if ($props[$this->proptags['recurring']] !== false) { |
|
| 3635 | + return false; |
|
| 3636 | + } |
|
| 3637 | + |
|
| 3638 | + if ($store === false) { |
|
| 3639 | + // If Delegate is processing Meeting Request/Response for Delegator then retrieve Delegator's store and calendar. |
|
| 3640 | + if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 3641 | + $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]); |
|
| 3642 | + $store = $delegatorStore['store']; |
|
| 3643 | + } |
|
| 3644 | + else { |
|
| 3645 | + $store = $this->store; |
|
| 3646 | + } |
|
| 3647 | + } |
|
| 3648 | + |
|
| 3649 | + $recurr = new Recurrence($store, $recurringMessage); |
|
| 3650 | + $attach = $recurr->getExceptionAttachment($basedate); |
|
| 3651 | + if ($attach) { |
|
| 3652 | + $occurItem = mapi_attach_openobj($attach); |
|
| 3653 | + } |
|
| 3654 | + |
|
| 3655 | + return $occurItem; |
|
| 3656 | + } |
|
| 3657 | + |
|
| 3658 | + /** |
|
| 3659 | + * Function which checks whether received meeting request is either conflicting with other appointments or not. |
|
| 3660 | + * |
|
| 3661 | + * @param MAPIMessage $message meeting request item that should be checked for conflicts in calendar |
|
| 3662 | + * @param MAPIStore $userStore store containing calendar folder that will be used for confilict checking |
|
| 3663 | + * @param MAPIFolder $calFolder calendar folder for conflict checking |
|
| 3664 | + * |
|
| 3665 | + * @return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances |
|
| 3666 | + * conflict of recurring meeting and false if meeting is not conflicting |
|
| 3667 | + * @return mixed if boolean then true/false for indicating conflict, if number then items that are conflicting with the message |
|
| 3668 | + */ |
|
| 3669 | + public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false) { |
|
| 3670 | + $returnValue = false; |
|
| 3671 | + $noOfInstances = 0; |
|
| 3672 | + |
|
| 3673 | + if ($message === false) { |
|
| 3674 | + $message = $this->message; |
|
| 3675 | + } |
|
| 3676 | + |
|
| 3677 | + $messageProps = mapi_getprops( |
|
| 3678 | + $message, |
|
| 3679 | + [ |
|
| 3680 | + PR_MESSAGE_CLASS, |
|
| 3681 | + $this->proptags['goid'], |
|
| 3682 | + $this->proptags['goid2'], |
|
| 3683 | + $this->proptags['startdate'], |
|
| 3684 | + $this->proptags['duedate'], |
|
| 3685 | + $this->proptags['recurring'], |
|
| 3686 | + $this->proptags['clipstart'], |
|
| 3687 | + $this->proptags['clipend'], |
|
| 3688 | + PR_RCVD_REPRESENTING_ENTRYID, |
|
| 3689 | + $this->proptags['basedate'], |
|
| 3690 | + PR_RCVD_REPRESENTING_NAME, |
|
| 3691 | + ] |
|
| 3692 | + ); |
|
| 3693 | + |
|
| 3694 | + if ($userStore === false) { |
|
| 3695 | + $userStore = $this->store; |
|
| 3696 | + |
|
| 3697 | + // check if delegate is processing the response |
|
| 3698 | + if (isset($messageProps[PR_RCVD_REPRESENTING_ENTRYID])) { |
|
| 3699 | + $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID], [PR_IPM_APPOINTMENT_ENTRYID]); |
|
| 3700 | + |
|
| 3701 | + $userStore = $delegatorStore['store']; |
|
| 3702 | + $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
|
| 3703 | + } |
|
| 3704 | + } |
|
| 3705 | + |
|
| 3706 | + if ($calFolder === false) { |
|
| 3707 | + $calFolder = $this->openDefaultCalendar($userStore); |
|
| 3708 | + } |
|
| 3709 | + |
|
| 3710 | + if ($calFolder) { |
|
| 3711 | + // Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar. |
|
| 3712 | + if (isset($messageProps[$this->proptags['recurring']]) && $messageProps[$this->proptags['recurring']] === true) { |
|
| 3713 | + // Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date') |
|
| 3714 | + $recurr = new Recurrence($userStore, $message); |
|
| 3715 | + $items = $recurr->getItems($messageProps[$this->proptags['clipstart']], $messageProps[$this->proptags['clipend']] * (24 * 24 * 60), 30); |
|
| 3716 | + |
|
| 3717 | + foreach ($items as $item) { |
|
| 3718 | + // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item |
|
| 3719 | + $calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]); |
|
| 3720 | + |
|
| 3721 | + foreach ($calendarItems as $calendarItem) { |
|
| 3722 | + if ($calendarItem[$this->proptags['busystatus']] !== fbFree) { |
|
| 3723 | + /* |
|
| 3724 | 3724 | * Only meeting requests have globalID, normal appointments do not have globalID |
| 3725 | 3725 | * so if any normal appointment if found then it is assumed to be conflict. |
| 3726 | 3726 | */ |
| 3727 | - if (isset($calendarItem[$this->proptags['goid']])) { |
|
| 3728 | - if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) { |
|
| 3729 | - ++$noOfInstances; |
|
| 3730 | - |
|
| 3731 | - break; |
|
| 3732 | - } |
|
| 3733 | - } |
|
| 3734 | - else { |
|
| 3735 | - ++$noOfInstances; |
|
| 3736 | - |
|
| 3737 | - break; |
|
| 3738 | - } |
|
| 3739 | - } |
|
| 3740 | - } |
|
| 3741 | - } |
|
| 3742 | - |
|
| 3743 | - if ($noOfInstances > 0) { |
|
| 3744 | - $returnValue = $noOfInstances; |
|
| 3745 | - } |
|
| 3746 | - } |
|
| 3747 | - else { |
|
| 3748 | - // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item |
|
| 3749 | - $items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]); |
|
| 3750 | - |
|
| 3751 | - if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) { |
|
| 3752 | - $basedate = $messageProps[$this->proptags['basedate']]; |
|
| 3753 | - // Get the goid2 from recurring MR which further used to |
|
| 3754 | - // check the resource conflicts item. |
|
| 3755 | - $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]); |
|
| 3756 | - $messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate); |
|
| 3757 | - $messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']]; |
|
| 3758 | - } |
|
| 3759 | - |
|
| 3760 | - foreach ($items as $item) { |
|
| 3761 | - if ($item[$this->proptags['busystatus']] !== fbFree) { |
|
| 3762 | - if (isset($item[$this->proptags['goid']])) { |
|
| 3763 | - if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) && |
|
| 3764 | - ($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) { |
|
| 3765 | - $returnValue = true; |
|
| 3766 | - |
|
| 3767 | - break; |
|
| 3768 | - } |
|
| 3769 | - } |
|
| 3770 | - else { |
|
| 3771 | - $returnValue = true; |
|
| 3772 | - |
|
| 3773 | - break; |
|
| 3774 | - } |
|
| 3775 | - } |
|
| 3776 | - } |
|
| 3777 | - } |
|
| 3778 | - } |
|
| 3779 | - |
|
| 3780 | - return $returnValue; |
|
| 3781 | - } |
|
| 3782 | - |
|
| 3783 | - /** |
|
| 3784 | - * Function which adds organizer to recipient list which is passed. |
|
| 3785 | - * This function also checks if it has organizer. |
|
| 3786 | - * |
|
| 3787 | - * @param array $messageProps message properties |
|
| 3788 | - * @param array $recipients recipients list of message |
|
| 3789 | - * @param bool $isException true if we are processing recipient of exception |
|
| 3790 | - */ |
|
| 3791 | - public function addDelegator($messageProps, &$recipients) { |
|
| 3792 | - $hasDelegator = false; |
|
| 3793 | - // Check if meeting already has an organizer. |
|
| 3794 | - foreach ($recipients as $key => $recipient) { |
|
| 3795 | - if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) { |
|
| 3796 | - $hasDelegator = true; |
|
| 3797 | - } |
|
| 3798 | - } |
|
| 3799 | - |
|
| 3800 | - if (!$hasDelegator) { |
|
| 3801 | - // Create delegator. |
|
| 3802 | - $delegator = []; |
|
| 3803 | - $delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID]; |
|
| 3804 | - $delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME]; |
|
| 3805 | - $delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]; |
|
| 3806 | - $delegator[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 3807 | - $delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME]; |
|
| 3808 | - $delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]; |
|
| 3809 | - $delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 3810 | - $delegator[PR_RECIPIENT_FLAGS] = recipSendable; |
|
| 3811 | - $delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY]; |
|
| 3812 | - |
|
| 3813 | - // Add organizer to recipients list. |
|
| 3814 | - array_unshift($recipients, $delegator); |
|
| 3815 | - } |
|
| 3816 | - } |
|
| 3817 | - |
|
| 3818 | - /** |
|
| 3819 | - * Function will return delegator's store and calendar folder for processing meetings. |
|
| 3820 | - * |
|
| 3821 | - * @param string $receivedRepresentingEnryid entryid of the delegator user |
|
| 3822 | - * @param array $foldersToOpen contains list of folder types that should be returned in result |
|
| 3823 | - * @param mixed $receivedRepresentingEntryId |
|
| 3824 | - * |
|
| 3825 | - * @return array contains store of the delegator and resource of folders if $foldersToOpen is not empty |
|
| 3826 | - */ |
|
| 3827 | - public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []) { |
|
| 3828 | - $returnData = []; |
|
| 3829 | - |
|
| 3830 | - $delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId); |
|
| 3831 | - $returnData['store'] = $delegatorStore; |
|
| 3832 | - |
|
| 3833 | - if (!empty($foldersToOpen)) { |
|
| 3834 | - for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) { |
|
| 3835 | - $folderType = $foldersToOpen[$index]; |
|
| 3836 | - |
|
| 3837 | - // first try with default folders |
|
| 3838 | - $folder = $this->openDefaultFolder($folderType, $delegatorStore); |
|
| 3839 | - |
|
| 3840 | - // if folder not found then try with base folders |
|
| 3841 | - if ($folder === false) { |
|
| 3842 | - $folder = $this->openBaseFolder($folderType, $delegatorStore); |
|
| 3843 | - } |
|
| 3844 | - |
|
| 3845 | - if ($folder === false) { |
|
| 3846 | - // we are still not able to get the folder so give up |
|
| 3847 | - continue; |
|
| 3848 | - } |
|
| 3849 | - |
|
| 3850 | - $returnData[$folderType] = $folder; |
|
| 3851 | - } |
|
| 3852 | - } |
|
| 3853 | - |
|
| 3854 | - return $returnData; |
|
| 3855 | - } |
|
| 3856 | - |
|
| 3857 | - /** |
|
| 3858 | - * Function returns extra info about meeting timing along with message body |
|
| 3859 | - * which will be included in body while sending meeting request/response. |
|
| 3860 | - * |
|
| 3861 | - * @return string $meetingTimeInfo info about meeting timing along with message body |
|
| 3862 | - */ |
|
| 3863 | - public function getMeetingTimeInfo() { |
|
| 3864 | - return $this->meetingTimeInfo; |
|
| 3865 | - } |
|
| 3866 | - |
|
| 3867 | - /** |
|
| 3868 | - * Function sets extra info about meeting timing along with message body |
|
| 3869 | - * which will be included in body while sending meeting request/response. |
|
| 3870 | - * |
|
| 3871 | - * @param string $meetingTimeInfo info about meeting timing along with message body |
|
| 3872 | - */ |
|
| 3873 | - public function setMeetingTimeInfo($meetingTimeInfo) { |
|
| 3874 | - $this->meetingTimeInfo = $meetingTimeInfo; |
|
| 3875 | - } |
|
| 3876 | - |
|
| 3877 | - /** |
|
| 3878 | - * Helper function which is use to get local categories of all occurrence. |
|
| 3879 | - * |
|
| 3880 | - * @param MAPIMessage $calendarItem meeting request item |
|
| 3881 | - * @param MAPIStore $store store containing calendar folder |
|
| 3882 | - * @param MAPIFolder $calFolder calendar folder |
|
| 3883 | - * |
|
| 3884 | - * @return array $localCategories which contain array of basedate along with categories |
|
| 3885 | - */ |
|
| 3886 | - public function getLocalCategories($calendarItem, $store, $calFolder) { |
|
| 3887 | - $calendarItemProps = mapi_getprops($calendarItem); |
|
| 3888 | - $recurrence = new Recurrence($store, $calendarItem); |
|
| 3889 | - |
|
| 3890 | - // Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date') |
|
| 3891 | - $items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30); |
|
| 3892 | - $localCategories = []; |
|
| 3893 | - |
|
| 3894 | - foreach ($items as $item) { |
|
| 3895 | - $recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]); |
|
| 3896 | - foreach ($recurrenceItems as $recurrenceItem) { |
|
| 3897 | - // Check if occurrence is exception then get the local categories of that occurrence. |
|
| 3898 | - if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) { |
|
| 3899 | - $exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']); |
|
| 3900 | - |
|
| 3901 | - if ($exceptionAttach) { |
|
| 3902 | - $exception = mapi_attach_openobj($exceptionAttach, 0); |
|
| 3903 | - $exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]); |
|
| 3904 | - if (isset($exceptionProps[$this->proptags['categories']])) { |
|
| 3905 | - $localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']]; |
|
| 3906 | - } |
|
| 3907 | - } |
|
| 3908 | - } |
|
| 3909 | - } |
|
| 3910 | - } |
|
| 3911 | - |
|
| 3912 | - return $localCategories; |
|
| 3913 | - } |
|
| 3914 | - |
|
| 3915 | - /** |
|
| 3916 | - * Helper function which is use to apply local categories on respective occurrences. |
|
| 3917 | - * |
|
| 3918 | - * @param MAPIMessage $calendarItem meeting request item |
|
| 3919 | - * @param MAPIStore $store store containing calendar folder |
|
| 3920 | - * @param array $localCategories array contains basedate and array of categories |
|
| 3921 | - */ |
|
| 3922 | - public function applyLocalCategories($calendarItem, $store, $localCategories) { |
|
| 3923 | - $calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]); |
|
| 3924 | - $message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]); |
|
| 3925 | - $recurrence = new Recurrence($store, $message); |
|
| 3926 | - |
|
| 3927 | - // Check for all occurrence if it is exception then modify the exception by setting up categories, |
|
| 3928 | - // Otherwise create new exception with categories. |
|
| 3929 | - foreach ($localCategories as $key => $value) { |
|
| 3930 | - if ($recurrence->isException($key)) { |
|
| 3931 | - $recurrence->modifyException([$this->proptags['categories'] => $value], $key); |
|
| 3932 | - } |
|
| 3933 | - else { |
|
| 3934 | - $recurrence->createException([$this->proptags['categories'] => $value], $key, false); |
|
| 3935 | - } |
|
| 3936 | - mapi_savechanges($message); |
|
| 3937 | - } |
|
| 3938 | - } |
|
| 3727 | + if (isset($calendarItem[$this->proptags['goid']])) { |
|
| 3728 | + if ($calendarItem[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) { |
|
| 3729 | + ++$noOfInstances; |
|
| 3730 | + |
|
| 3731 | + break; |
|
| 3732 | + } |
|
| 3733 | + } |
|
| 3734 | + else { |
|
| 3735 | + ++$noOfInstances; |
|
| 3736 | + |
|
| 3737 | + break; |
|
| 3738 | + } |
|
| 3739 | + } |
|
| 3740 | + } |
|
| 3741 | + } |
|
| 3742 | + |
|
| 3743 | + if ($noOfInstances > 0) { |
|
| 3744 | + $returnValue = $noOfInstances; |
|
| 3745 | + } |
|
| 3746 | + } |
|
| 3747 | + else { |
|
| 3748 | + // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item |
|
| 3749 | + $items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]); |
|
| 3750 | + |
|
| 3751 | + if (isset($messageProps[$this->proptags['basedate']]) && !empty($messageProps[$this->proptags['basedate']])) { |
|
| 3752 | + $basedate = $messageProps[$this->proptags['basedate']]; |
|
| 3753 | + // Get the goid2 from recurring MR which further used to |
|
| 3754 | + // check the resource conflicts item. |
|
| 3755 | + $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid2']]); |
|
| 3756 | + $messageProps[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid2']], $basedate); |
|
| 3757 | + $messageProps[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']]; |
|
| 3758 | + } |
|
| 3759 | + |
|
| 3760 | + foreach ($items as $item) { |
|
| 3761 | + if ($item[$this->proptags['busystatus']] !== fbFree) { |
|
| 3762 | + if (isset($item[$this->proptags['goid']])) { |
|
| 3763 | + if (($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid']]) && |
|
| 3764 | + ($item[$this->proptags['goid']] !== $messageProps[$this->proptags['goid2']])) { |
|
| 3765 | + $returnValue = true; |
|
| 3766 | + |
|
| 3767 | + break; |
|
| 3768 | + } |
|
| 3769 | + } |
|
| 3770 | + else { |
|
| 3771 | + $returnValue = true; |
|
| 3772 | + |
|
| 3773 | + break; |
|
| 3774 | + } |
|
| 3775 | + } |
|
| 3776 | + } |
|
| 3777 | + } |
|
| 3778 | + } |
|
| 3779 | + |
|
| 3780 | + return $returnValue; |
|
| 3781 | + } |
|
| 3782 | + |
|
| 3783 | + /** |
|
| 3784 | + * Function which adds organizer to recipient list which is passed. |
|
| 3785 | + * This function also checks if it has organizer. |
|
| 3786 | + * |
|
| 3787 | + * @param array $messageProps message properties |
|
| 3788 | + * @param array $recipients recipients list of message |
|
| 3789 | + * @param bool $isException true if we are processing recipient of exception |
|
| 3790 | + */ |
|
| 3791 | + public function addDelegator($messageProps, &$recipients) { |
|
| 3792 | + $hasDelegator = false; |
|
| 3793 | + // Check if meeting already has an organizer. |
|
| 3794 | + foreach ($recipients as $key => $recipient) { |
|
| 3795 | + if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) { |
|
| 3796 | + $hasDelegator = true; |
|
| 3797 | + } |
|
| 3798 | + } |
|
| 3799 | + |
|
| 3800 | + if (!$hasDelegator) { |
|
| 3801 | + // Create delegator. |
|
| 3802 | + $delegator = []; |
|
| 3803 | + $delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID]; |
|
| 3804 | + $delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME]; |
|
| 3805 | + $delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]; |
|
| 3806 | + $delegator[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 3807 | + $delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME]; |
|
| 3808 | + $delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]; |
|
| 3809 | + $delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 3810 | + $delegator[PR_RECIPIENT_FLAGS] = recipSendable; |
|
| 3811 | + $delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY]; |
|
| 3812 | + |
|
| 3813 | + // Add organizer to recipients list. |
|
| 3814 | + array_unshift($recipients, $delegator); |
|
| 3815 | + } |
|
| 3816 | + } |
|
| 3817 | + |
|
| 3818 | + /** |
|
| 3819 | + * Function will return delegator's store and calendar folder for processing meetings. |
|
| 3820 | + * |
|
| 3821 | + * @param string $receivedRepresentingEnryid entryid of the delegator user |
|
| 3822 | + * @param array $foldersToOpen contains list of folder types that should be returned in result |
|
| 3823 | + * @param mixed $receivedRepresentingEntryId |
|
| 3824 | + * |
|
| 3825 | + * @return array contains store of the delegator and resource of folders if $foldersToOpen is not empty |
|
| 3826 | + */ |
|
| 3827 | + public function getDelegatorStore($receivedRepresentingEntryId, $foldersToOpen = []) { |
|
| 3828 | + $returnData = []; |
|
| 3829 | + |
|
| 3830 | + $delegatorStore = $this->openCustomUserStore($receivedRepresentingEntryId); |
|
| 3831 | + $returnData['store'] = $delegatorStore; |
|
| 3832 | + |
|
| 3833 | + if (!empty($foldersToOpen)) { |
|
| 3834 | + for ($index = 0, $len = count($foldersToOpen); $index < $len; ++$index) { |
|
| 3835 | + $folderType = $foldersToOpen[$index]; |
|
| 3836 | + |
|
| 3837 | + // first try with default folders |
|
| 3838 | + $folder = $this->openDefaultFolder($folderType, $delegatorStore); |
|
| 3839 | + |
|
| 3840 | + // if folder not found then try with base folders |
|
| 3841 | + if ($folder === false) { |
|
| 3842 | + $folder = $this->openBaseFolder($folderType, $delegatorStore); |
|
| 3843 | + } |
|
| 3844 | + |
|
| 3845 | + if ($folder === false) { |
|
| 3846 | + // we are still not able to get the folder so give up |
|
| 3847 | + continue; |
|
| 3848 | + } |
|
| 3849 | + |
|
| 3850 | + $returnData[$folderType] = $folder; |
|
| 3851 | + } |
|
| 3852 | + } |
|
| 3853 | + |
|
| 3854 | + return $returnData; |
|
| 3855 | + } |
|
| 3856 | + |
|
| 3857 | + /** |
|
| 3858 | + * Function returns extra info about meeting timing along with message body |
|
| 3859 | + * which will be included in body while sending meeting request/response. |
|
| 3860 | + * |
|
| 3861 | + * @return string $meetingTimeInfo info about meeting timing along with message body |
|
| 3862 | + */ |
|
| 3863 | + public function getMeetingTimeInfo() { |
|
| 3864 | + return $this->meetingTimeInfo; |
|
| 3865 | + } |
|
| 3866 | + |
|
| 3867 | + /** |
|
| 3868 | + * Function sets extra info about meeting timing along with message body |
|
| 3869 | + * which will be included in body while sending meeting request/response. |
|
| 3870 | + * |
|
| 3871 | + * @param string $meetingTimeInfo info about meeting timing along with message body |
|
| 3872 | + */ |
|
| 3873 | + public function setMeetingTimeInfo($meetingTimeInfo) { |
|
| 3874 | + $this->meetingTimeInfo = $meetingTimeInfo; |
|
| 3875 | + } |
|
| 3876 | + |
|
| 3877 | + /** |
|
| 3878 | + * Helper function which is use to get local categories of all occurrence. |
|
| 3879 | + * |
|
| 3880 | + * @param MAPIMessage $calendarItem meeting request item |
|
| 3881 | + * @param MAPIStore $store store containing calendar folder |
|
| 3882 | + * @param MAPIFolder $calFolder calendar folder |
|
| 3883 | + * |
|
| 3884 | + * @return array $localCategories which contain array of basedate along with categories |
|
| 3885 | + */ |
|
| 3886 | + public function getLocalCategories($calendarItem, $store, $calFolder) { |
|
| 3887 | + $calendarItemProps = mapi_getprops($calendarItem); |
|
| 3888 | + $recurrence = new Recurrence($store, $calendarItem); |
|
| 3889 | + |
|
| 3890 | + // Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date') |
|
| 3891 | + $items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30); |
|
| 3892 | + $localCategories = []; |
|
| 3893 | + |
|
| 3894 | + foreach ($items as $item) { |
|
| 3895 | + $recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags['categories']]); |
|
| 3896 | + foreach ($recurrenceItems as $recurrenceItem) { |
|
| 3897 | + // Check if occurrence is exception then get the local categories of that occurrence. |
|
| 3898 | + if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) { |
|
| 3899 | + $exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']); |
|
| 3900 | + |
|
| 3901 | + if ($exceptionAttach) { |
|
| 3902 | + $exception = mapi_attach_openobj($exceptionAttach, 0); |
|
| 3903 | + $exceptionProps = mapi_getprops($exception, [$this->proptags['categories']]); |
|
| 3904 | + if (isset($exceptionProps[$this->proptags['categories']])) { |
|
| 3905 | + $localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags['categories']]; |
|
| 3906 | + } |
|
| 3907 | + } |
|
| 3908 | + } |
|
| 3909 | + } |
|
| 3910 | + } |
|
| 3911 | + |
|
| 3912 | + return $localCategories; |
|
| 3913 | + } |
|
| 3914 | + |
|
| 3915 | + /** |
|
| 3916 | + * Helper function which is use to apply local categories on respective occurrences. |
|
| 3917 | + * |
|
| 3918 | + * @param MAPIMessage $calendarItem meeting request item |
|
| 3919 | + * @param MAPIStore $store store containing calendar folder |
|
| 3920 | + * @param array $localCategories array contains basedate and array of categories |
|
| 3921 | + */ |
|
| 3922 | + public function applyLocalCategories($calendarItem, $store, $localCategories) { |
|
| 3923 | + $calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]); |
|
| 3924 | + $message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]); |
|
| 3925 | + $recurrence = new Recurrence($store, $message); |
|
| 3926 | + |
|
| 3927 | + // Check for all occurrence if it is exception then modify the exception by setting up categories, |
|
| 3928 | + // Otherwise create new exception with categories. |
|
| 3929 | + foreach ($localCategories as $key => $value) { |
|
| 3930 | + if ($recurrence->isException($key)) { |
|
| 3931 | + $recurrence->modifyException([$this->proptags['categories'] => $value], $key); |
|
| 3932 | + } |
|
| 3933 | + else { |
|
| 3934 | + $recurrence->createException([$this->proptags['categories'] => $value], $key, false); |
|
| 3935 | + } |
|
| 3936 | + mapi_savechanges($message); |
|
| 3937 | + } |
|
| 3938 | + } |
|
| 3939 | 3939 | } |
@@ -152,15 +152,15 @@ discard block |
||
| 152 | 152 | $properties['reminderminutes'] = 'PT_LONG:PSETID_Common:0x8501'; |
| 153 | 153 | $properties['reminderset'] = 'PT_BOOLEAN:PSETID_Common:0x8503'; |
| 154 | 154 | $properties['sendasical'] = 'PT_BOOLEAN:PSETID_Appointment:0x8200'; |
| 155 | - $properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201'; // AppointmentSequenceNumber |
|
| 156 | - $properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203'; // AppointmentLastSequence |
|
| 155 | + $properties['updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8201'; // AppointmentSequenceNumber |
|
| 156 | + $properties['last_updatecounter'] = 'PT_LONG:PSETID_Appointment:0x8203'; // AppointmentLastSequence |
|
| 157 | 157 | $properties['unknown7'] = 'PT_LONG:PSETID_Appointment:0x8202'; |
| 158 | 158 | $properties['busystatus'] = 'PT_LONG:PSETID_Appointment:0x8205'; |
| 159 | 159 | $properties['intendedbusystatus'] = 'PT_LONG:PSETID_Appointment:0x8224'; |
| 160 | 160 | $properties['start'] = 'PT_SYSTIME:PSETID_Appointment:0x820d'; |
| 161 | 161 | $properties['responselocation'] = 'PT_STRING8:PSETID_Meeting:0x2'; |
| 162 | 162 | $properties['location'] = 'PT_STRING8:PSETID_Appointment:0x8208'; |
| 163 | - $properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229'; // PidLidFInvited, MeetingRequestWasSent |
|
| 163 | + $properties['requestsent'] = 'PT_BOOLEAN:PSETID_Appointment:0x8229'; // PidLidFInvited, MeetingRequestWasSent |
|
| 164 | 164 | $properties['startdate'] = 'PT_SYSTIME:PSETID_Appointment:0x820d'; |
| 165 | 165 | $properties['duedate'] = 'PT_SYSTIME:PSETID_Appointment:0x820e'; |
| 166 | 166 | $properties['flagdueby'] = 'PT_SYSTIME:PSETID_Common:0x8560'; |
@@ -169,11 +169,11 @@ discard block |
||
| 169 | 169 | $properties['recurring'] = 'PT_BOOLEAN:PSETID_Appointment:0x8223'; |
| 170 | 170 | $properties['clipstart'] = 'PT_SYSTIME:PSETID_Appointment:0x8235'; |
| 171 | 171 | $properties['clipend'] = 'PT_SYSTIME:PSETID_Appointment:0x8236'; |
| 172 | - $properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD'; // StartRecurTime |
|
| 173 | - $properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE'; // StartRecurTime |
|
| 174 | - $properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF'; // EndRecurDate |
|
| 175 | - $properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10'; // EndRecurTime |
|
| 176 | - $properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA'; // LID_IS_EXCEPTION |
|
| 172 | + $properties['start_recur_date'] = 'PT_LONG:PSETID_Meeting:0xD'; // StartRecurTime |
|
| 173 | + $properties['start_recur_time'] = 'PT_LONG:PSETID_Meeting:0xE'; // StartRecurTime |
|
| 174 | + $properties['end_recur_date'] = 'PT_LONG:PSETID_Meeting:0xF'; // EndRecurDate |
|
| 175 | + $properties['end_recur_time'] = 'PT_LONG:PSETID_Meeting:0x10'; // EndRecurTime |
|
| 176 | + $properties['is_exception'] = 'PT_BOOLEAN:PSETID_Meeting:0xA'; // LID_IS_EXCEPTION |
|
| 177 | 177 | $properties['apptreplyname'] = 'PT_STRING8:PSETID_Appointment:0x8230'; |
| 178 | 178 | // Propose new time properties |
| 179 | 179 | $properties['proposed_start_whole'] = 'PT_SYSTIME:PSETID_Appointment:0x8250'; |
@@ -545,7 +545,7 @@ discard block |
||
| 545 | 545 | $listProperties['rcvd_representing_search_key'] = PR_RCVD_REPRESENTING_SEARCH_KEY; |
| 546 | 546 | $messageProps = mapi_getprops($this->message, $listProperties); |
| 547 | 547 | |
| 548 | - $goid = $messageProps[$this->proptags['goid']]; // GlobalID (0x3) |
|
| 548 | + $goid = $messageProps[$this->proptags['goid']]; // GlobalID (0x3) |
|
| 549 | 549 | if (!isset($goid)) { |
| 550 | 550 | return; |
| 551 | 551 | } |
@@ -1436,7 +1436,7 @@ discard block |
||
| 1436 | 1436 | $props[$this->proptags['goid2']] = $goid; |
| 1437 | 1437 | |
| 1438 | 1438 | if (!isset($props[$this->proptags['updatecounter']])) { |
| 1439 | - $props[$this->proptags['updatecounter']] = 0; // OL also starts sequence no with zero. |
|
| 1439 | + $props[$this->proptags['updatecounter']] = 0; // OL also starts sequence no with zero. |
|
| 1440 | 1440 | $props[$this->proptags['last_updatecounter']] = 0; |
| 1441 | 1441 | } |
| 1442 | 1442 | |
@@ -2074,9 +2074,9 @@ discard block |
||
| 2074 | 2074 | $subjectprefix = _('New Time Proposed'); |
| 2075 | 2075 | } |
| 2076 | 2076 | |
| 2077 | - $props[PR_SUBJECT] = $subjectprefix . ': ' . $messageprops[PR_SUBJECT]; |
|
| 2077 | + $props[PR_SUBJECT] = $subjectprefix.': '.$messageprops[PR_SUBJECT]; |
|
| 2078 | 2078 | |
| 2079 | - $props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.' . $classpostfix; |
|
| 2079 | + $props[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Resp.'.$classpostfix; |
|
| 2080 | 2080 | if (isset($messageprops[PR_OWNER_APPT_ID])) { |
| 2081 | 2081 | $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID]; |
| 2082 | 2082 | } |
@@ -2285,12 +2285,12 @@ discard block |
||
| 2285 | 2285 | $hasOrganizer = false; |
| 2286 | 2286 | // Check if meeting already has an organizer. |
| 2287 | 2287 | foreach ($recipients as $key => $recipient) { |
| 2288 | - if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
|
| 2288 | + if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable|recipOrganizer)) { |
|
| 2289 | 2289 | $hasOrganizer = true; |
| 2290 | 2290 | } |
| 2291 | 2291 | elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
| 2292 | 2292 | // Recipients for an occurrence |
| 2293 | - $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
|
| 2293 | + $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalResponse; |
|
| 2294 | 2294 | } |
| 2295 | 2295 | } |
| 2296 | 2296 | |
@@ -2304,7 +2304,7 @@ discard block |
||
| 2304 | 2304 | $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
| 2305 | 2305 | $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE]; |
| 2306 | 2306 | $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
| 2307 | - $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer; |
|
| 2307 | + $organizer[PR_RECIPIENT_FLAGS] = recipSendable|recipOrganizer; |
|
| 2308 | 2308 | $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY]; |
| 2309 | 2309 | |
| 2310 | 2310 | // Add organizer to recipients list. |
@@ -2362,7 +2362,7 @@ discard block |
||
| 2362 | 2362 | $month = $basedate ? sprintf('%02s', dechex(gmdate('m', $basedate))) : '00'; |
| 2363 | 2363 | $day = $basedate ? sprintf('%02s', dechex(gmdate('d', $basedate))) : '00'; |
| 2364 | 2364 | |
| 2365 | - return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40))); |
|
| 2365 | + return hex2bin(strtoupper(substr($hexguid, 0, 32).$year.$month.$day.substr($hexguid, 40))); |
|
| 2366 | 2366 | } |
| 2367 | 2367 | |
| 2368 | 2368 | /** |
@@ -2400,7 +2400,7 @@ discard block |
||
| 2400 | 2400 | continue; |
| 2401 | 2401 | } |
| 2402 | 2402 | |
| 2403 | - $attachOld = mapi_message_openattach($copyFrom, (int) $attachProps[PR_ATTACH_NUM]); |
|
| 2403 | + $attachOld = mapi_message_openattach($copyFrom, (int)$attachProps[PR_ATTACH_NUM]); |
|
| 2404 | 2404 | $attachNewResourceMsg = mapi_message_createattach($copyTo); |
| 2405 | 2405 | mapi_copyto($attachOld, [], [], $attachNewResourceMsg, 0); |
| 2406 | 2406 | mapi_savechanges($attachNewResourceMsg); |
@@ -2491,7 +2491,7 @@ discard block |
||
| 2491 | 2491 | [ |
| 2492 | 2492 | RES_PROPERTY, |
| 2493 | 2493 | [ |
| 2494 | - RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2494 | + RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2495 | 2495 | ULPROPTAG => PR_RECIPIENT_TYPE, |
| 2496 | 2496 | VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
| 2497 | 2497 | ], |
@@ -2623,7 +2623,7 @@ discard block |
||
| 2623 | 2623 | |
| 2624 | 2624 | // Prefix the subject if needed |
| 2625 | 2625 | if ($prefix && isset($messageprops[PR_SUBJECT])) { |
| 2626 | - $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT]; |
|
| 2626 | + $messageprops[PR_SUBJECT] = $prefix.$messageprops[PR_SUBJECT]; |
|
| 2627 | 2627 | } |
| 2628 | 2628 | |
| 2629 | 2629 | // Set status to cancelled if needed |
@@ -2773,7 +2773,7 @@ discard block |
||
| 2773 | 2773 | [ |
| 2774 | 2774 | RES_PROPERTY, |
| 2775 | 2775 | [ |
| 2776 | - RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2776 | + RELOP => RELOP_EQ, // Equals recipient type 3: Resource |
|
| 2777 | 2777 | ULPROPTAG => PR_RECIPIENT_TYPE, |
| 2778 | 2778 | VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
| 2779 | 2779 | ], |
@@ -3005,7 +3005,7 @@ discard block |
||
| 3005 | 3005 | $restriction[1][] = [ |
| 3006 | 3006 | RES_PROPERTY, |
| 3007 | 3007 | [ |
| 3008 | - RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3008 | + RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3009 | 3009 | ULPROPTAG => PR_RECIPIENT_TYPE, |
| 3010 | 3010 | VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
| 3011 | 3011 | ], |
@@ -3061,7 +3061,7 @@ discard block |
||
| 3061 | 3061 | |
| 3062 | 3062 | // Prefix the subject if needed |
| 3063 | 3063 | if ($prefix && isset($newmessageprops[PR_SUBJECT])) { |
| 3064 | - $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT]; |
|
| 3064 | + $newmessageprops[PR_SUBJECT] = $prefix.$newmessageprops[PR_SUBJECT]; |
|
| 3065 | 3065 | } |
| 3066 | 3066 | |
| 3067 | 3067 | if (isset($newmessageprops[$this->proptags['categories']]) && |
@@ -3102,7 +3102,7 @@ discard block |
||
| 3102 | 3102 | [ |
| 3103 | 3103 | RES_PROPERTY, |
| 3104 | 3104 | [ |
| 3105 | - RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3105 | + RELOP => RELOP_NE, // Does not equal recipient type: MAPI_BCC (Resource) |
|
| 3106 | 3106 | ULPROPTAG => PR_RECIPIENT_TYPE, |
| 3107 | 3107 | VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC], |
| 3108 | 3108 | ], |
@@ -3209,9 +3209,9 @@ discard block |
||
| 3209 | 3209 | $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled'; |
| 3210 | 3210 | $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request |
| 3211 | 3211 | $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free |
| 3212 | - $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance |
|
| 3212 | + $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH; // HIGH Importance |
|
| 3213 | 3213 | if (isset($newmessageprops[PR_SUBJECT])) { |
| 3214 | - $newmessageprops[PR_SUBJECT] = _('Canceled: ') . $newmessageprops[PR_SUBJECT]; |
|
| 3214 | + $newmessageprops[PR_SUBJECT] = _('Canceled: ').$newmessageprops[PR_SUBJECT]; |
|
| 3215 | 3215 | } |
| 3216 | 3216 | |
| 3217 | 3217 | mapi_setprops($new, $newmessageprops); |
@@ -3269,12 +3269,12 @@ discard block |
||
| 3269 | 3269 | |
| 3270 | 3270 | // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds |
| 3271 | 3271 | // RecurStartDate = year * 512 + month_number * 32 + day_number |
| 3272 | - $newmessageprops[$this->proptags['start_recur_date']] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]); |
|
| 3272 | + $newmessageprops[$this->proptags['start_recur_date']] = (((int)$startDate[0]) * 512) + (((int)$startDate[1]) * 32) + ((int)$startDate[2]); |
|
| 3273 | 3273 | // RecurStartTime = hour * 4096 + minutes * 64 + seconds |
| 3274 | - $newmessageprops[$this->proptags['start_recur_time']] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]); |
|
| 3274 | + $newmessageprops[$this->proptags['start_recur_time']] = (((int)$startDate[3]) * 4096) + (((int)$startDate[4]) * 64) + ((int)$startDate[5]); |
|
| 3275 | 3275 | |
| 3276 | - $newmessageprops[$this->proptags['end_recur_date']] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]); |
|
| 3277 | - $newmessageprops[$this->proptags['end_recur_time']] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]); |
|
| 3276 | + $newmessageprops[$this->proptags['end_recur_date']] = (((int)$endDate[0]) * 512) + (((int)$endDate[1]) * 32) + ((int)$endDate[2]); |
|
| 3277 | + $newmessageprops[$this->proptags['end_recur_time']] = (((int)$endDate[3]) * 4096) + (((int)$endDate[4]) * 64) + ((int)$endDate[5]); |
|
| 3278 | 3278 | } |
| 3279 | 3279 | } |
| 3280 | 3280 | |
@@ -326,8 +326,7 @@ discard block |
||
| 326 | 326 | |
| 327 | 327 | $userStore = $delegatorStore['store']; |
| 328 | 328 | $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
| 329 | - } |
|
| 330 | - else { |
|
| 329 | + } else { |
|
| 331 | 330 | $userStore = $this->store; |
| 332 | 331 | $calFolder = $this->openDefaultCalendar(); |
| 333 | 332 | } |
@@ -396,8 +395,7 @@ discard block |
||
| 396 | 395 | // Create/modify exception |
| 397 | 396 | if ($recurr->isException($basedate)) { |
| 398 | 397 | $recurr->modifyException($exception_props, $basedate); |
| 399 | - } |
|
| 400 | - else { |
|
| 398 | + } else { |
|
| 401 | 399 | // When we are creating an exception we need copy recipients from main recurring item |
| 402 | 400 | $recipTable = mapi_message_getrecipienttable($calendarItem); |
| 403 | 401 | $recips = mapi_table_queryallrows($recipTable, $this->recipprops); |
@@ -415,8 +413,7 @@ discard block |
||
| 415 | 413 | if ($attach) { |
| 416 | 414 | $recurringItem = $calendarItem; |
| 417 | 415 | $calendarItem = mapi_attach_openobj($attach, MAPI_MODIFY); |
| 418 | - } |
|
| 419 | - else { |
|
| 416 | + } else { |
|
| 420 | 417 | return false; |
| 421 | 418 | } |
| 422 | 419 | } |
@@ -499,8 +496,7 @@ discard block |
||
| 499 | 496 | $props = []; |
| 500 | 497 | if ($messageprops[$this->proptags['counter_proposal']]) { |
| 501 | 498 | $props[$this->proptags['counter_proposal']] = true; |
| 502 | - } |
|
| 503 | - else { |
|
| 499 | + } else { |
|
| 504 | 500 | $props[$this->proptags['counter_proposal']] = false; |
| 505 | 501 | } |
| 506 | 502 | |
@@ -556,8 +552,7 @@ discard block |
||
| 556 | 552 | |
| 557 | 553 | $store = $delegatorStore['store']; |
| 558 | 554 | $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
| 559 | - } |
|
| 560 | - else { |
|
| 555 | + } else { |
|
| 561 | 556 | $store = $this->store; |
| 562 | 557 | $calFolder = $this->openDefaultCalendar(); |
| 563 | 558 | } |
@@ -585,13 +580,11 @@ discard block |
||
| 585 | 580 | |
| 586 | 581 | if ($recurr->isException($basedate)) { |
| 587 | 582 | $recurr->modifyException($messageProps, $basedate); |
| 588 | - } |
|
| 589 | - else { |
|
| 583 | + } else { |
|
| 590 | 584 | $recurr->createException($messageProps, $basedate); |
| 591 | 585 | } |
| 592 | 586 | } |
| 593 | - } |
|
| 594 | - else { |
|
| 587 | + } else { |
|
| 595 | 588 | // set the properties of the cancellation object |
| 596 | 589 | mapi_setprops($calendarItem, $messageProps); |
| 597 | 590 | } |
@@ -645,8 +638,7 @@ discard block |
||
| 645 | 638 | |
| 646 | 639 | $store = $delegatorStore['store']; |
| 647 | 640 | $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
| 648 | - } |
|
| 649 | - else { |
|
| 641 | + } else { |
|
| 650 | 642 | $calFolder = $this->openDefaultCalendar(); |
| 651 | 643 | $store = $this->store; |
| 652 | 644 | } |
@@ -707,8 +699,7 @@ discard block |
||
| 707 | 699 | $senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID]; |
| 708 | 700 | if (isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) { |
| 709 | 701 | $entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate); |
| 710 | - } |
|
| 711 | - else { |
|
| 702 | + } else { |
|
| 712 | 703 | $entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate); |
| 713 | 704 | } |
| 714 | 705 | |
@@ -762,8 +753,7 @@ discard block |
||
| 762 | 753 | if (!$calendarItem) { |
| 763 | 754 | // Recurring item not found, so create new meeting in Calendar |
| 764 | 755 | $calendarItem = mapi_folder_createmessage($calFolder); |
| 765 | - } |
|
| 766 | - else { |
|
| 756 | + } else { |
|
| 767 | 757 | // we have found the main recurring item, check if this meeting request is already processed |
| 768 | 758 | if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) { |
| 769 | 759 | // only set required properties, other properties are already copied when processing this meeting request |
@@ -784,8 +774,7 @@ discard block |
||
| 784 | 774 | if (isset($props[$this->proptags['reminderminutes']])) { |
| 785 | 775 | $props[$this->proptags['flagdueby']] = $props[$this->proptags['startdate']] - ($props[$this->proptags['reminderminutes']] * 60); |
| 786 | 776 | } |
| 787 | - } |
|
| 788 | - else { |
|
| 777 | + } else { |
|
| 789 | 778 | // only get required properties so we will not overwrite existing updated properties from calendar |
| 790 | 779 | $props = mapi_getprops($this->message, [PR_ENTRYID]); |
| 791 | 780 | } |
@@ -806,13 +795,11 @@ discard block |
||
| 806 | 795 | if (isset($props[$this->proptags['intendedbusystatus']])) { |
| 807 | 796 | if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) { |
| 808 | 797 | $props[$this->proptags['busystatus']] = fbTentative; |
| 809 | - } |
|
| 810 | - else { |
|
| 798 | + } else { |
|
| 811 | 799 | $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']]; |
| 812 | 800 | } |
| 813 | 801 | // we already have intendedbusystatus value in $props so no need to copy it |
| 814 | - } |
|
| 815 | - else { |
|
| 802 | + } else { |
|
| 816 | 803 | $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
| 817 | 804 | } |
| 818 | 805 | |
@@ -875,8 +862,7 @@ discard block |
||
| 875 | 862 | } |
| 876 | 863 | |
| 877 | 864 | $entryid = $props[PR_ENTRYID]; |
| 878 | - } |
|
| 879 | - else { |
|
| 865 | + } else { |
|
| 880 | 866 | /** |
| 881 | 867 | * This meeting request is not recurring, so can be an exception or normal meeting. |
| 882 | 868 | * If exception then find main recurring item and update exception |
@@ -943,13 +929,11 @@ discard block |
||
| 943 | 929 | if (isset($messageprops[$this->proptags['intendedbusystatus']])) { |
| 944 | 930 | if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) { |
| 945 | 931 | $calItemProps[$this->proptags['busystatus']] = fbTentative; |
| 946 | - } |
|
| 947 | - else { |
|
| 932 | + } else { |
|
| 948 | 933 | $calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
| 949 | 934 | } |
| 950 | 935 | $calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
| 951 | - } |
|
| 952 | - else { |
|
| 936 | + } else { |
|
| 953 | 937 | $calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
| 954 | 938 | } |
| 955 | 939 | |
@@ -982,8 +966,7 @@ discard block |
||
| 982 | 966 | |
| 983 | 967 | $messageprops = mapi_getprops($calmsg, [PR_ENTRYID]); |
| 984 | 968 | $entryid = $messageprops[PR_ENTRYID]; |
| 985 | - } |
|
| 986 | - else { |
|
| 969 | + } else { |
|
| 987 | 970 | // Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment |
| 988 | 971 | $new = mapi_folder_createmessage($calFolder); |
| 989 | 972 | $props = mapi_getprops($this->message); |
@@ -1018,13 +1001,11 @@ discard block |
||
| 1018 | 1001 | if (isset($props[$this->proptags['intendedbusystatus']])) { |
| 1019 | 1002 | if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) { |
| 1020 | 1003 | $props[$this->proptags['busystatus']] = fbTentative; |
| 1021 | - } |
|
| 1022 | - else { |
|
| 1004 | + } else { |
|
| 1023 | 1005 | $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']]; |
| 1024 | 1006 | } |
| 1025 | 1007 | // we already have intendedbusystatus value in $props so no need to copy it |
| 1026 | - } |
|
| 1027 | - else { |
|
| 1008 | + } else { |
|
| 1028 | 1009 | $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
| 1029 | 1010 | } |
| 1030 | 1011 | |
@@ -1056,8 +1037,7 @@ discard block |
||
| 1056 | 1037 | ], |
| 1057 | 1038 | ]; |
| 1058 | 1039 | $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res); |
| 1059 | - } |
|
| 1060 | - else { |
|
| 1040 | + } else { |
|
| 1061 | 1041 | $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops); |
| 1062 | 1042 | } |
| 1063 | 1043 | $this->addOrganizer($props, $recipients); |
@@ -1069,8 +1049,7 @@ discard block |
||
| 1069 | 1049 | } |
| 1070 | 1050 | } |
| 1071 | 1051 | } |
| 1072 | - } |
|
| 1073 | - else { |
|
| 1052 | + } else { |
|
| 1074 | 1053 | // Here only properties are set on calendaritem, because user is responding from calendar. |
| 1075 | 1054 | $props = []; |
| 1076 | 1055 | $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted; |
@@ -1078,13 +1057,11 @@ discard block |
||
| 1078 | 1057 | if (isset($messageprops[$this->proptags['intendedbusystatus']])) { |
| 1079 | 1058 | if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) { |
| 1080 | 1059 | $props[$this->proptags['busystatus']] = fbTentative; |
| 1081 | - } |
|
| 1082 | - else { |
|
| 1060 | + } else { |
|
| 1083 | 1061 | $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
| 1084 | 1062 | } |
| 1085 | 1063 | $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']]; |
| 1086 | - } |
|
| 1087 | - else { |
|
| 1064 | + } else { |
|
| 1088 | 1065 | $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
| 1089 | 1066 | } |
| 1090 | 1067 | |
@@ -1107,8 +1084,7 @@ discard block |
||
| 1107 | 1084 | |
| 1108 | 1085 | if ($recurr->isException($basedate)) { |
| 1109 | 1086 | $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips); |
| 1110 | - } |
|
| 1111 | - else { |
|
| 1087 | + } else { |
|
| 1112 | 1088 | $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
| 1113 | 1089 | $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate); |
| 1114 | 1090 | |
@@ -1120,8 +1096,7 @@ discard block |
||
| 1120 | 1096 | |
| 1121 | 1097 | $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips); |
| 1122 | 1098 | } |
| 1123 | - } |
|
| 1124 | - else { |
|
| 1099 | + } else { |
|
| 1125 | 1100 | mapi_setprops($this->message, $proposeNewTimeProps + $props); |
| 1126 | 1101 | } |
| 1127 | 1102 | mapi_savechanges($this->message); |
@@ -1162,8 +1137,7 @@ discard block |
||
| 1162 | 1137 | |
| 1163 | 1138 | $store = $delegatorStore['store']; |
| 1164 | 1139 | $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
| 1165 | - } |
|
| 1166 | - else { |
|
| 1140 | + } else { |
|
| 1167 | 1141 | $calFolder = $this->openDefaultCalendar(); |
| 1168 | 1142 | $store = $this->store; |
| 1169 | 1143 | } |
@@ -1255,8 +1229,7 @@ discard block |
||
| 1255 | 1229 | |
| 1256 | 1230 | $store = $delegatorStore['store']; |
| 1257 | 1231 | $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
| 1258 | - } |
|
| 1259 | - else { |
|
| 1232 | + } else { |
|
| 1260 | 1233 | $store = $this->store; |
| 1261 | 1234 | $calFolder = $this->openDefaultCalendar(); |
| 1262 | 1235 | } |
@@ -1288,8 +1261,7 @@ discard block |
||
| 1288 | 1261 | // exception found, remove it from calendar |
| 1289 | 1262 | $this->doRemoveExceptionFromCalendar($basedate, $calendarItem, $store); |
| 1290 | 1263 | } |
| 1291 | - } |
|
| 1292 | - else { |
|
| 1264 | + } else { |
|
| 1293 | 1265 | // remove normal / recurring series from calendar |
| 1294 | 1266 | $entryids = mapi_getprops($calendarItem, [PR_ENTRYID]); |
| 1295 | 1267 | |
@@ -1304,14 +1276,12 @@ discard block |
||
| 1304 | 1276 | |
| 1305 | 1277 | // Move the cancellation mail to wastebasket |
| 1306 | 1278 | mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
| 1307 | - } |
|
| 1308 | - else { |
|
| 1279 | + } else { |
|
| 1309 | 1280 | // Here only properties are set on calendaritem, because user is responding from calendar. |
| 1310 | 1281 | if ($basedate) { |
| 1311 | 1282 | // remove the occurrence |
| 1312 | 1283 | $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store); |
| 1313 | - } |
|
| 1314 | - else { |
|
| 1284 | + } else { |
|
| 1315 | 1285 | // remove normal/recurring meeting item. |
| 1316 | 1286 | // Move the message to the waste basket |
| 1317 | 1287 | mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE); |
@@ -1358,8 +1328,7 @@ discard block |
||
| 1358 | 1328 | |
| 1359 | 1329 | // save changes in the message |
| 1360 | 1330 | mapi_savechanges($this->message); |
| 1361 | - } |
|
| 1362 | - else { |
|
| 1331 | + } else { |
|
| 1363 | 1332 | // cancellation of normal meeting request |
| 1364 | 1333 | // Send the cancellation |
| 1365 | 1334 | $this->updateMeetingRequest(); |
@@ -1499,8 +1468,7 @@ discard block |
||
| 1499 | 1468 | } |
| 1500 | 1469 | } |
| 1501 | 1470 | } |
| 1502 | - } |
|
| 1503 | - else { |
|
| 1471 | + } else { |
|
| 1504 | 1472 | // Basedate found, an exception is to be send |
| 1505 | 1473 | if ($basedate) { |
| 1506 | 1474 | $recurr = new Recurrence($this->openDefaultStore(), $this->message); |
@@ -1508,8 +1476,7 @@ discard block |
||
| 1508 | 1476 | if ($cancel) { |
| 1509 | 1477 | // @TODO: remove occurrence from Resource's Calendar if resource was booked for whole series |
| 1510 | 1478 | $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false); |
| 1511 | - } |
|
| 1512 | - else { |
|
| 1479 | + } else { |
|
| 1513 | 1480 | $attach = $recurr->getExceptionAttachment($basedate); |
| 1514 | 1481 | |
| 1515 | 1482 | if ($attach) { |
@@ -1528,8 +1495,7 @@ discard block |
||
| 1528 | 1495 | } |
| 1529 | 1496 | } |
| 1530 | 1497 | } |
| 1531 | - } |
|
| 1532 | - else { |
|
| 1498 | + } else { |
|
| 1533 | 1499 | // This is normal meeting |
| 1534 | 1500 | $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix); |
| 1535 | 1501 | |
@@ -1606,8 +1572,7 @@ discard block |
||
| 1606 | 1572 | |
| 1607 | 1573 | if (!isset($messageprops[$this->proptags['goid']])) { |
| 1608 | 1574 | $this->setMeetingRequest($basedate); |
| 1609 | - } |
|
| 1610 | - else { |
|
| 1575 | + } else { |
|
| 1611 | 1576 | $counter = $messageprops[$this->proptags['last_updatecounter']] + 1; |
| 1612 | 1577 | |
| 1613 | 1578 | // increment value of last_updatecounter, last_updatecounter will be common for recurring series |
@@ -1626,8 +1591,7 @@ discard block |
||
| 1626 | 1591 | if (!$this->isMeetingRequest($props[PR_MESSAGE_CLASS]) && !$this->isMeetingRequestResponse($props[PR_MESSAGE_CLASS]) && !$this->isMeetingCancellation($props[PR_MESSAGE_CLASS])) { |
| 1627 | 1592 | // we are checking with calendar item |
| 1628 | 1593 | $calendarItem = $this->message; |
| 1629 | - } |
|
| 1630 | - else { |
|
| 1594 | + } else { |
|
| 1631 | 1595 | // we are checking with meeting request / response / cancellation mail |
| 1632 | 1596 | // get calendar items |
| 1633 | 1597 | $calendarItem = $this->getCorrespondentCalendarItem(true); |
@@ -1762,8 +1726,7 @@ discard block |
||
| 1762 | 1726 | if (isset($inboxprops[$prop])) { |
| 1763 | 1727 | return $inboxprops[$prop]; |
| 1764 | 1728 | } |
| 1765 | - } |
|
| 1766 | - catch (MAPIException $e) { |
|
| 1729 | + } catch (MAPIException $e) { |
|
| 1767 | 1730 | // public store doesn't support this method |
| 1768 | 1731 | if ($e->getCode() == MAPI_E_NO_SUPPORT) { |
| 1769 | 1732 | // don't propagate this error to parent handlers, if store doesn't support it |
@@ -1853,8 +1816,7 @@ discard block |
||
| 1853 | 1816 | if (($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) === MAPI_ACCESS_CREATE_CONTENTS) { |
| 1854 | 1817 | $accessToFolder = true; |
| 1855 | 1818 | } |
| 1856 | - } |
|
| 1857 | - catch (MAPIException $e) { |
|
| 1819 | + } catch (MAPIException $e) { |
|
| 1858 | 1820 | // we don't have rights to open folder, so return false |
| 1859 | 1821 | if ($e->getCode() == MAPI_E_NO_ACCESS) { |
| 1860 | 1822 | return $accessToFolder; |
@@ -1884,8 +1846,7 @@ discard block |
||
| 1884 | 1846 | $delegatorStore = $this->getDelegatorStore($messageProps[PR_RCVD_REPRESENTING_ENTRYID]); |
| 1885 | 1847 | |
| 1886 | 1848 | $store = $delegatorStore['store']; |
| 1887 | - } |
|
| 1888 | - else { |
|
| 1849 | + } else { |
|
| 1889 | 1850 | $store = $this->store; |
| 1890 | 1851 | } |
| 1891 | 1852 | } |
@@ -1895,8 +1856,7 @@ discard block |
||
| 1895 | 1856 | if (isset($provider[PR_MDB_PROVIDER]) && $provider[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) { |
| 1896 | 1857 | $entryid = mapi_getprops($this->message, [PR_PARENT_ENTRYID]); |
| 1897 | 1858 | $entryid = $entryid[PR_PARENT_ENTRYID]; |
| 1898 | - } |
|
| 1899 | - else { |
|
| 1859 | + } else { |
|
| 1900 | 1860 | $entryid = $this->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store); |
| 1901 | 1861 | if ($entryid === false) { |
| 1902 | 1862 | $entryid = $this->getBaseEntryID(PR_IPM_APPOINTMENT_ENTRYID, $store); |
@@ -1922,8 +1882,7 @@ discard block |
||
| 1922 | 1882 | |
| 1923 | 1883 | try { |
| 1924 | 1884 | $mailuser = mapi_ab_openentry($ab, $ownerentryid); |
| 1925 | - } |
|
| 1926 | - catch (MAPIException $e) { |
|
| 1885 | + } catch (MAPIException $e) { |
|
| 1927 | 1886 | return; |
| 1928 | 1887 | } |
| 1929 | 1888 | |
@@ -1991,8 +1950,7 @@ discard block |
||
| 1991 | 1950 | $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']]; |
| 1992 | 1951 | $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']]; |
| 1993 | 1952 | $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT]; |
| 1994 | - } |
|
| 1995 | - else { |
|
| 1953 | + } else { |
|
| 1996 | 1954 | // Exceptions is deleted. |
| 1997 | 1955 | // Update $messageprops with timings of occurrence |
| 1998 | 1956 | $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate); |
@@ -2004,8 +1962,7 @@ discard block |
||
| 2004 | 1962 | |
| 2005 | 1963 | $props[$this->proptags['recurring']] = false; |
| 2006 | 1964 | $props[$this->proptags['is_exception']] = true; |
| 2007 | - } |
|
| 2008 | - else { |
|
| 1965 | + } else { |
|
| 2009 | 1966 | // we are creating a response from meeting request mail (it could be recurring or non-recurring) |
| 2010 | 1967 | // Send all recurrence info in response, if this is a recurrence meeting. |
| 2011 | 1968 | $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]; |
@@ -2287,8 +2244,7 @@ discard block |
||
| 2287 | 2244 | foreach ($recipients as $key => $recipient) { |
| 2288 | 2245 | if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
| 2289 | 2246 | $hasOrganizer = true; |
| 2290 | - } |
|
| 2291 | - elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 2247 | + } elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 2292 | 2248 | // Recipients for an occurrence |
| 2293 | 2249 | $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
| 2294 | 2250 | } |
@@ -2429,8 +2385,7 @@ discard block |
||
| 2429 | 2385 | ], |
| 2430 | 2386 | ]; |
| 2431 | 2387 | $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops, $res); |
| 2432 | - } |
|
| 2433 | - else { |
|
| 2388 | + } else { |
|
| 2434 | 2389 | $recipients = mapi_table_queryallrows($recipientTable, $this->recipprops); |
| 2435 | 2390 | } |
| 2436 | 2391 | |
@@ -2526,8 +2481,7 @@ discard block |
||
| 2526 | 2481 | if ($accessToFolder) { |
| 2527 | 2482 | $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]); |
| 2528 | 2483 | } |
| 2529 | - } |
|
| 2530 | - catch (MAPIException $e) { |
|
| 2484 | + } catch (MAPIException $e) { |
|
| 2531 | 2485 | $e->setHandled(); |
| 2532 | 2486 | $this->errorSetResource = 1; // No access |
| 2533 | 2487 | } |
@@ -2554,8 +2508,7 @@ discard block |
||
| 2554 | 2508 | */ |
| 2555 | 2509 | // $errorSetResource = 2; |
| 2556 | 2510 | $this->nonAcceptingResources[] = $resourceRecipients[$i]; |
| 2557 | - } |
|
| 2558 | - else { |
|
| 2511 | + } else { |
|
| 2559 | 2512 | if ($declineRecurringMeetingRequests && !$cancel) { |
| 2560 | 2513 | // Check if appointment is recurring |
| 2561 | 2514 | if ($messageprops[$this->proptags['recurring']]) { |
@@ -2616,8 +2569,7 @@ discard block |
||
| 2616 | 2569 | if (!$newResourceMsg) { |
| 2617 | 2570 | $newResourceMsg = mapi_folder_createmessage($calFolder); |
| 2618 | 2571 | } |
| 2619 | - } |
|
| 2620 | - else { |
|
| 2572 | + } else { |
|
| 2621 | 2573 | $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]); |
| 2622 | 2574 | } |
| 2623 | 2575 | |
@@ -2631,8 +2583,7 @@ discard block |
||
| 2631 | 2583 | if ($cancel) { |
| 2632 | 2584 | $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled |
| 2633 | 2585 | $messageprops[$this->proptags['busystatus']] = fbFree; // Free |
| 2634 | - } |
|
| 2635 | - else { |
|
| 2586 | + } else { |
|
| 2636 | 2587 | $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request |
| 2637 | 2588 | } |
| 2638 | 2589 | $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment |
@@ -2676,8 +2627,7 @@ discard block |
||
| 2676 | 2627 | $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid; |
| 2677 | 2628 | $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey; |
| 2678 | 2629 | } |
| 2679 | - } |
|
| 2680 | - else { |
|
| 2630 | + } else { |
|
| 2681 | 2631 | // get organizer information |
| 2682 | 2632 | $addrinfo = $this->getOwnerAddress($this->store); |
| 2683 | 2633 | |
@@ -2713,12 +2663,10 @@ discard block |
||
| 2713 | 2663 | // Update occurrence |
| 2714 | 2664 | if ($recurr->isException($basedate)) { |
| 2715 | 2665 | $recurr->modifyException($messageprops, $basedate, $recips); |
| 2716 | - } |
|
| 2717 | - else { |
|
| 2666 | + } else { |
|
| 2718 | 2667 | $recurr->createException($messageprops, $basedate, false, $recips); |
| 2719 | 2668 | } |
| 2720 | - } |
|
| 2721 | - else { |
|
| 2669 | + } else { |
|
| 2722 | 2670 | mapi_setprops($newResourceMsg, $messageprops); |
| 2723 | 2671 | |
| 2724 | 2672 | // Copy attachments |
@@ -2742,8 +2690,7 @@ discard block |
||
| 2742 | 2690 | 'msg' => $newResourceMsg, |
| 2743 | 2691 | ]; |
| 2744 | 2692 | $this->includesResources = true; |
| 2745 | - } |
|
| 2746 | - else { |
|
| 2693 | + } else { |
|
| 2747 | 2694 | /* |
| 2748 | 2695 | * If no other errors occurred and you have no access to the |
| 2749 | 2696 | * folder of the resource, throw an error=1. |
@@ -2838,8 +2785,7 @@ discard block |
||
| 2838 | 2785 | ], |
| 2839 | 2786 | ]; |
| 2840 | 2787 | $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res); |
| 2841 | - } |
|
| 2842 | - else { |
|
| 2788 | + } else { |
|
| 2843 | 2789 | $recips = mapi_table_queryallrows($reciptable, $this->recipprops); |
| 2844 | 2790 | } |
| 2845 | 2791 | |
@@ -2857,13 +2803,11 @@ discard block |
||
| 2857 | 2803 | if (isset($exception_props[$this->proptags['intendedbusystatus']])) { |
| 2858 | 2804 | if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) { |
| 2859 | 2805 | $exception_props[$this->proptags['busystatus']] = fbTentative; |
| 2860 | - } |
|
| 2861 | - else { |
|
| 2806 | + } else { |
|
| 2862 | 2807 | $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']]; |
| 2863 | 2808 | } |
| 2864 | 2809 | // we already have intendedbusystatus value in $exception_props so no need to copy it |
| 2865 | - } |
|
| 2866 | - else { |
|
| 2810 | + } else { |
|
| 2867 | 2811 | $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy; |
| 2868 | 2812 | } |
| 2869 | 2813 | |
@@ -2879,8 +2823,7 @@ discard block |
||
| 2879 | 2823 | |
| 2880 | 2824 | if ($recurr->isException($basedate)) { |
| 2881 | 2825 | $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem); |
| 2882 | - } |
|
| 2883 | - else { |
|
| 2826 | + } else { |
|
| 2884 | 2827 | $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem); |
| 2885 | 2828 | } |
| 2886 | 2829 | |
@@ -2917,8 +2860,7 @@ discard block |
||
| 2917 | 2860 | |
| 2918 | 2861 | if ($recurr->isException($basedate)) { |
| 2919 | 2862 | $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem); |
| 2920 | - } |
|
| 2921 | - else { |
|
| 2863 | + } else { |
|
| 2922 | 2864 | $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem); |
| 2923 | 2865 | } |
| 2924 | 2866 | |
@@ -3017,8 +2959,7 @@ discard block |
||
| 3017 | 2959 | |
| 3018 | 2960 | if (!$deletedRecips) { |
| 3019 | 2961 | $deletedRecips = array_merge([], $recipients); |
| 3020 | - } |
|
| 3021 | - else { |
|
| 2962 | + } else { |
|
| 3022 | 2963 | $deletedRecips = array_merge($deletedRecips, $recipients); |
| 3023 | 2964 | } |
| 3024 | 2965 | } |
@@ -3157,8 +3098,7 @@ discard block |
||
| 3157 | 3098 | $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Canceled'; |
| 3158 | 3099 | $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request |
| 3159 | 3100 | $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free |
| 3160 | - } |
|
| 3161 | - else { |
|
| 3101 | + } else { |
|
| 3162 | 3102 | $newmessageprops[PR_MESSAGE_CLASS] = 'IPM.Schedule.Meeting.Request'; |
| 3163 | 3103 | $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request |
| 3164 | 3104 | } |
@@ -3327,8 +3267,7 @@ discard block |
||
| 3327 | 3267 | $sentprops[PR_SENDER_SEARCH_KEY] = $ownersearchkey; |
| 3328 | 3268 | } |
| 3329 | 3269 | } |
| 3330 | - } |
|
| 3331 | - else { |
|
| 3270 | + } else { |
|
| 3332 | 3271 | // normal user is sending mail, so both set of properties will be same |
| 3333 | 3272 | $userDetails = $this->getOwnerAddress($userStore); |
| 3334 | 3273 | |
@@ -3494,8 +3433,7 @@ discard block |
||
| 3494 | 3433 | $message = mapi_attach_openobj($attach, MAPI_MODIFY); |
| 3495 | 3434 | } |
| 3496 | 3435 | } |
| 3497 | - } |
|
| 3498 | - else { |
|
| 3436 | + } else { |
|
| 3499 | 3437 | // use normal message or recurring series message |
| 3500 | 3438 | $message = $this->message; |
| 3501 | 3439 | } |
@@ -3538,8 +3476,7 @@ discard block |
||
| 3538 | 3476 | if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) { |
| 3539 | 3477 | // Recipient is attendee, set the trackstatus to 'Not Responded' |
| 3540 | 3478 | $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
| 3541 | - } |
|
| 3542 | - else { |
|
| 3479 | + } else { |
|
| 3543 | 3480 | // Recipient is organizer, this is not possible, but for safety |
| 3544 | 3481 | // it is best to clear the trackstatus for him as well by setting |
| 3545 | 3482 | // the trackstatus to 'Organized'. |
@@ -3577,8 +3514,7 @@ discard block |
||
| 3577 | 3514 | |
| 3578 | 3515 | $store = $delegatorStore['store']; |
| 3579 | 3516 | $calFolder = $delegatorStore[PR_IPM_APPOINTMENT_ENTRYID]; |
| 3580 | - } |
|
| 3581 | - else { |
|
| 3517 | + } else { |
|
| 3582 | 3518 | $store = $this->store; |
| 3583 | 3519 | $calFolder = $this->openDefaultCalendar(); |
| 3584 | 3520 | } |
@@ -3640,8 +3576,7 @@ discard block |
||
| 3640 | 3576 | if (isset($props[PR_RCVD_REPRESENTING_ENTRYID])) { |
| 3641 | 3577 | $delegatorStore = $this->getDelegatorStore($props[PR_RCVD_REPRESENTING_ENTRYID]); |
| 3642 | 3578 | $store = $delegatorStore['store']; |
| 3643 | - } |
|
| 3644 | - else { |
|
| 3579 | + } else { |
|
| 3645 | 3580 | $store = $this->store; |
| 3646 | 3581 | } |
| 3647 | 3582 | } |
@@ -3730,8 +3665,7 @@ discard block |
||
| 3730 | 3665 | |
| 3731 | 3666 | break; |
| 3732 | 3667 | } |
| 3733 | - } |
|
| 3734 | - else { |
|
| 3668 | + } else { |
|
| 3735 | 3669 | ++$noOfInstances; |
| 3736 | 3670 | |
| 3737 | 3671 | break; |
@@ -3743,8 +3677,7 @@ discard block |
||
| 3743 | 3677 | if ($noOfInstances > 0) { |
| 3744 | 3678 | $returnValue = $noOfInstances; |
| 3745 | 3679 | } |
| 3746 | - } |
|
| 3747 | - else { |
|
| 3680 | + } else { |
|
| 3748 | 3681 | // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item |
| 3749 | 3682 | $items = getCalendarItems($userStore, $calFolder, $messageProps[$this->proptags['startdate']], $messageProps[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus']]); |
| 3750 | 3683 | |
@@ -3766,8 +3699,7 @@ discard block |
||
| 3766 | 3699 | |
| 3767 | 3700 | break; |
| 3768 | 3701 | } |
| 3769 | - } |
|
| 3770 | - else { |
|
| 3702 | + } else { |
|
| 3771 | 3703 | $returnValue = true; |
| 3772 | 3704 | |
| 3773 | 3705 | break; |
@@ -3929,8 +3861,7 @@ discard block |
||
| 3929 | 3861 | foreach ($localCategories as $key => $value) { |
| 3930 | 3862 | if ($recurrence->isException($key)) { |
| 3931 | 3863 | $recurrence->modifyException([$this->proptags['categories'] => $value], $key); |
| 3932 | - } |
|
| 3933 | - else { |
|
| 3864 | + } else { |
|
| 3934 | 3865 | $recurrence->createException([$this->proptags['categories'] => $value], $key, false); |
| 3935 | 3866 | } |
| 3936 | 3867 | mapi_savechanges($message); |
@@ -5,411 +5,411 @@ |
||
| 5 | 5 | * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | - class TaskRecurrence extends BaseRecurrence { |
|
| 9 | - /** |
|
| 10 | - * Timezone info which is always false for task. |
|
| 11 | - */ |
|
| 12 | - public $tz = false; |
|
| 13 | - |
|
| 14 | - private $action; |
|
| 15 | - |
|
| 16 | - public function __construct($store, $message) { |
|
| 17 | - $this->store = $store; |
|
| 18 | - $this->message = $message; |
|
| 19 | - |
|
| 20 | - $properties = []; |
|
| 21 | - $properties["entryid"] = PR_ENTRYID; |
|
| 22 | - $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
|
| 23 | - $properties["icon_index"] = PR_ICON_INDEX; |
|
| 24 | - $properties["message_class"] = PR_MESSAGE_CLASS; |
|
| 25 | - $properties["message_flags"] = PR_MESSAGE_FLAGS; |
|
| 26 | - $properties["subject"] = PR_SUBJECT; |
|
| 27 | - $properties["importance"] = PR_IMPORTANCE; |
|
| 28 | - $properties["sensitivity"] = PR_SENSITIVITY; |
|
| 29 | - $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME; |
|
| 30 | - $properties["status"] = "PT_LONG:PSETID_Task:0x8101"; |
|
| 31 | - $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102"; |
|
| 32 | - $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104"; |
|
| 33 | - $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105"; |
|
| 34 | - $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107"; |
|
| 35 | - $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109"; |
|
| 36 | - $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f"; |
|
| 37 | - $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116"; |
|
| 38 | - $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110"; |
|
| 39 | - $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111"; |
|
| 40 | - $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c"; |
|
| 41 | - $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e"; |
|
| 42 | - $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f"; |
|
| 43 | - $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126"; |
|
| 44 | - |
|
| 45 | - $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 46 | - $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502"; |
|
| 47 | - $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 48 | - |
|
| 49 | - $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; |
|
| 50 | - $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
|
| 51 | - $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
|
| 52 | - $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 53 | - |
|
| 54 | - $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
|
| 55 | - $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
|
| 56 | - $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518"; |
|
| 57 | - $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560"; |
|
| 58 | - $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510"; |
|
| 59 | - $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 60 | - $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 61 | - |
|
| 62 | - $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 63 | - |
|
| 64 | - parent::__construct($store, $message, $properties); |
|
| 65 | - } |
|
| 66 | - |
|
| 67 | - /** |
|
| 68 | - * Function which saves recurrence and also regenerates task if necessary. |
|
| 69 | - * |
|
| 70 | - *@param array $recur new recurrence properties |
|
| 71 | - * |
|
| 72 | - *@return array of properties of regenerated task else false |
|
| 73 | - */ |
|
| 74 | - public function setRecurrence(&$recur) { |
|
| 75 | - $this->recur = $recur; |
|
| 76 | - $this->action = &$recur; |
|
| 77 | - |
|
| 78 | - if (!isset($this->recur["changed_occurrences"])) { |
|
| 79 | - $this->recur["changed_occurrences"] = []; |
|
| 80 | - } |
|
| 81 | - |
|
| 82 | - if (!isset($this->recur["deleted_occurrences"])) { |
|
| 83 | - $this->recur["deleted_occurrences"] = []; |
|
| 84 | - } |
|
| 85 | - |
|
| 86 | - if (!isset($this->recur['startocc'])) { |
|
| 87 | - $this->recur['startocc'] = 0; |
|
| 88 | - } |
|
| 89 | - if (!isset($this->recur['endocc'])) { |
|
| 90 | - $this->recur['endocc'] = 0; |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - // Save recurrence because we need proper startrecurrdate and endrecurrdate |
|
| 94 | - $this->saveRecurrence(); |
|
| 95 | - |
|
| 96 | - // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence |
|
| 97 | - $msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]); |
|
| 98 | - $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]); |
|
| 99 | - foreach ($recurring_data as $key => $value) { |
|
| 100 | - $this->recur[$key] = $value; |
|
| 101 | - } |
|
| 102 | - |
|
| 103 | - $this->setFirstOccurrence(); |
|
| 104 | - |
|
| 105 | - // Let's see if next occurrence has to be generated |
|
| 106 | - return $this->moveToNextOccurrence(); |
|
| 107 | - } |
|
| 108 | - |
|
| 109 | - /** |
|
| 110 | - * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence. |
|
| 111 | - */ |
|
| 112 | - public function setFirstOccurrence() { |
|
| 113 | - // Check if it is already the first occurrence |
|
| 114 | - if ($this->action['start'] == $this->recur["start"]) { |
|
| 115 | - return; |
|
| 116 | - } |
|
| 117 | - $items = $this->getNextOccurrence(); |
|
| 118 | - |
|
| 119 | - $props = []; |
|
| 120 | - $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']]; |
|
| 121 | - $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']]; |
|
| 122 | - |
|
| 123 | - $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']]; |
|
| 124 | - $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']]; |
|
| 125 | - |
|
| 126 | - mapi_setprops($this->message, $props); |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - /** |
|
| 130 | - * Function which creates new task as current occurrence and moves the |
|
| 131 | - * existing task to next occurrence. |
|
| 132 | - * |
|
| 133 | - *@param array $recur $action from client |
|
| 134 | - * |
|
| 135 | - *@return bool if moving to next occurrence succeed then it returns |
|
| 136 | - * properties of either newly created task or existing task ELSE |
|
| 137 | - * false because that was last occurrence |
|
| 138 | - */ |
|
| 139 | - public function moveToNextOccurrence() { |
|
| 140 | - $result = false; |
|
| 141 | - /* |
|
| 8 | + class TaskRecurrence extends BaseRecurrence { |
|
| 9 | + /** |
|
| 10 | + * Timezone info which is always false for task. |
|
| 11 | + */ |
|
| 12 | + public $tz = false; |
|
| 13 | + |
|
| 14 | + private $action; |
|
| 15 | + |
|
| 16 | + public function __construct($store, $message) { |
|
| 17 | + $this->store = $store; |
|
| 18 | + $this->message = $message; |
|
| 19 | + |
|
| 20 | + $properties = []; |
|
| 21 | + $properties["entryid"] = PR_ENTRYID; |
|
| 22 | + $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
|
| 23 | + $properties["icon_index"] = PR_ICON_INDEX; |
|
| 24 | + $properties["message_class"] = PR_MESSAGE_CLASS; |
|
| 25 | + $properties["message_flags"] = PR_MESSAGE_FLAGS; |
|
| 26 | + $properties["subject"] = PR_SUBJECT; |
|
| 27 | + $properties["importance"] = PR_IMPORTANCE; |
|
| 28 | + $properties["sensitivity"] = PR_SENSITIVITY; |
|
| 29 | + $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME; |
|
| 30 | + $properties["status"] = "PT_LONG:PSETID_Task:0x8101"; |
|
| 31 | + $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102"; |
|
| 32 | + $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104"; |
|
| 33 | + $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105"; |
|
| 34 | + $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107"; |
|
| 35 | + $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109"; |
|
| 36 | + $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f"; |
|
| 37 | + $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116"; |
|
| 38 | + $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110"; |
|
| 39 | + $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111"; |
|
| 40 | + $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c"; |
|
| 41 | + $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e"; |
|
| 42 | + $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f"; |
|
| 43 | + $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126"; |
|
| 44 | + |
|
| 45 | + $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 46 | + $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502"; |
|
| 47 | + $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 48 | + |
|
| 49 | + $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; |
|
| 50 | + $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
|
| 51 | + $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
|
| 52 | + $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 53 | + |
|
| 54 | + $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
|
| 55 | + $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
|
| 56 | + $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518"; |
|
| 57 | + $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560"; |
|
| 58 | + $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510"; |
|
| 59 | + $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 60 | + $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 61 | + |
|
| 62 | + $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 63 | + |
|
| 64 | + parent::__construct($store, $message, $properties); |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + /** |
|
| 68 | + * Function which saves recurrence and also regenerates task if necessary. |
|
| 69 | + * |
|
| 70 | + *@param array $recur new recurrence properties |
|
| 71 | + * |
|
| 72 | + *@return array of properties of regenerated task else false |
|
| 73 | + */ |
|
| 74 | + public function setRecurrence(&$recur) { |
|
| 75 | + $this->recur = $recur; |
|
| 76 | + $this->action = &$recur; |
|
| 77 | + |
|
| 78 | + if (!isset($this->recur["changed_occurrences"])) { |
|
| 79 | + $this->recur["changed_occurrences"] = []; |
|
| 80 | + } |
|
| 81 | + |
|
| 82 | + if (!isset($this->recur["deleted_occurrences"])) { |
|
| 83 | + $this->recur["deleted_occurrences"] = []; |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + if (!isset($this->recur['startocc'])) { |
|
| 87 | + $this->recur['startocc'] = 0; |
|
| 88 | + } |
|
| 89 | + if (!isset($this->recur['endocc'])) { |
|
| 90 | + $this->recur['endocc'] = 0; |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + // Save recurrence because we need proper startrecurrdate and endrecurrdate |
|
| 94 | + $this->saveRecurrence(); |
|
| 95 | + |
|
| 96 | + // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence |
|
| 97 | + $msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]); |
|
| 98 | + $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]); |
|
| 99 | + foreach ($recurring_data as $key => $value) { |
|
| 100 | + $this->recur[$key] = $value; |
|
| 101 | + } |
|
| 102 | + |
|
| 103 | + $this->setFirstOccurrence(); |
|
| 104 | + |
|
| 105 | + // Let's see if next occurrence has to be generated |
|
| 106 | + return $this->moveToNextOccurrence(); |
|
| 107 | + } |
|
| 108 | + |
|
| 109 | + /** |
|
| 110 | + * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence. |
|
| 111 | + */ |
|
| 112 | + public function setFirstOccurrence() { |
|
| 113 | + // Check if it is already the first occurrence |
|
| 114 | + if ($this->action['start'] == $this->recur["start"]) { |
|
| 115 | + return; |
|
| 116 | + } |
|
| 117 | + $items = $this->getNextOccurrence(); |
|
| 118 | + |
|
| 119 | + $props = []; |
|
| 120 | + $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']]; |
|
| 121 | + $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']]; |
|
| 122 | + |
|
| 123 | + $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']]; |
|
| 124 | + $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']]; |
|
| 125 | + |
|
| 126 | + mapi_setprops($this->message, $props); |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + /** |
|
| 130 | + * Function which creates new task as current occurrence and moves the |
|
| 131 | + * existing task to next occurrence. |
|
| 132 | + * |
|
| 133 | + *@param array $recur $action from client |
|
| 134 | + * |
|
| 135 | + *@return bool if moving to next occurrence succeed then it returns |
|
| 136 | + * properties of either newly created task or existing task ELSE |
|
| 137 | + * false because that was last occurrence |
|
| 138 | + */ |
|
| 139 | + public function moveToNextOccurrence() { |
|
| 140 | + $result = false; |
|
| 141 | + /* |
|
| 142 | 142 | * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date |
| 143 | 143 | * then we create first two occurrence separately and for first occurrence recurrence has ended. |
| 144 | 144 | */ |
| 145 | - if ((empty($this->action['startdate']) && empty($this->action['duedate'])) || |
|
| 146 | - ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) { |
|
| 147 | - $nextOccurrence = $this->getNextOccurrence(); |
|
| 148 | - $result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]); |
|
| 149 | - |
|
| 150 | - $props = []; |
|
| 151 | - if ($nextOccurrence) { |
|
| 152 | - if (!isset($this->action['deleteOccurrence'])) { |
|
| 153 | - // Create current occurrence as separate task |
|
| 154 | - $result = $this->regenerateTask($this->action['complete']); |
|
| 155 | - } |
|
| 156 | - |
|
| 157 | - // Set reminder for next occurrence |
|
| 158 | - $this->setReminder($nextOccurrence); |
|
| 159 | - |
|
| 160 | - // Update properties for next occurrence |
|
| 161 | - $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']]; |
|
| 162 | - $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']]; |
|
| 163 | - |
|
| 164 | - $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']]; |
|
| 165 | - $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']]; |
|
| 166 | - |
|
| 167 | - // If current task as been mark as 'Complete' then next occurrence should be incomplete. |
|
| 168 | - if (isset($this->action['complete']) && $this->action['complete'] == 1) { |
|
| 169 | - $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted; |
|
| 170 | - $this->action['complete'] = $props[$this->proptags["complete"]] = false; |
|
| 171 | - $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0; |
|
| 172 | - } |
|
| 173 | - |
|
| 174 | - $props[$this->proptags["dead_occurrence"]] = false; |
|
| 175 | - } |
|
| 176 | - else { |
|
| 177 | - if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) { |
|
| 178 | - return false; |
|
| 179 | - } |
|
| 180 | - |
|
| 181 | - // Didn't get next occurrence, probably this is the last one, so recurrence ends here |
|
| 182 | - $props[$this->proptags["dead_occurrence"]] = true; |
|
| 183 | - $props[$this->proptags["datecompleted"]] = $this->action['datecompleted']; |
|
| 184 | - $props[$this->proptags["task_f_creator"]] = true; |
|
| 185 | - |
|
| 186 | - // OL props |
|
| 187 | - $props[$this->proptags["side_effects"]] = 1296; |
|
| 188 | - $props[$this->proptags["icon_index"]] = 1280; |
|
| 189 | - } |
|
| 190 | - |
|
| 191 | - mapi_setprops($this->message, $props); |
|
| 192 | - } |
|
| 193 | - |
|
| 194 | - return $result; |
|
| 195 | - } |
|
| 196 | - |
|
| 197 | - /** |
|
| 198 | - * Function which return properties of next occurrence. |
|
| 199 | - * |
|
| 200 | - *@return array startdate/enddate of next occurrence |
|
| 201 | - */ |
|
| 202 | - public function getNextOccurrence() { |
|
| 203 | - if ($this->recur) { |
|
| 204 | - $items = []; |
|
| 205 | - |
|
| 206 | - // @TODO: fix start of range |
|
| 207 | - $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start']; |
|
| 208 | - $dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]); |
|
| 209 | - |
|
| 210 | - // Fix recur object |
|
| 211 | - $this->recur['startocc'] = 0; |
|
| 212 | - $this->recur['endocc'] = 0; |
|
| 213 | - |
|
| 214 | - // Retrieve next occurrence |
|
| 215 | - $items = $this->getItems($start, $dayend, 1); |
|
| 216 | - |
|
| 217 | - return !empty($items) ? $items[0] : false; |
|
| 218 | - } |
|
| 219 | - } |
|
| 220 | - |
|
| 221 | - /** |
|
| 222 | - * Function which clones current occurrence and sets appropriate properties. |
|
| 223 | - * The original recurring item is moved to next occurrence. |
|
| 224 | - * |
|
| 225 | - *@param bool $markComplete true if existing occurrence has to be mark complete else false |
|
| 226 | - */ |
|
| 227 | - public function regenerateTask($markComplete) { |
|
| 228 | - // Get all properties |
|
| 229 | - $taskItemProps = mapi_getprops($this->message); |
|
| 230 | - |
|
| 231 | - if (isset($this->action["subject"])) { |
|
| 232 | - $taskItemProps[$this->proptags["subject"]] = $this->action["subject"]; |
|
| 233 | - } |
|
| 234 | - if (isset($this->action["importance"])) { |
|
| 235 | - $taskItemProps[$this->proptags["importance"]] = $this->action["importance"]; |
|
| 236 | - } |
|
| 237 | - if (isset($this->action["startdate"])) { |
|
| 238 | - $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"]; |
|
| 239 | - $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"]; |
|
| 240 | - } |
|
| 241 | - if (isset($this->action["duedate"])) { |
|
| 242 | - $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"]; |
|
| 243 | - $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"]; |
|
| 244 | - } |
|
| 245 | - |
|
| 246 | - $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]); |
|
| 247 | - $newMessage = mapi_folder_createmessage($folder); |
|
| 248 | - |
|
| 249 | - $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted; |
|
| 250 | - $taskItemProps[$this->proptags["complete"]] = $markComplete; |
|
| 251 | - $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0; |
|
| 252 | - |
|
| 253 | - // This occurrence has been marked as 'Complete' so disable reminder |
|
| 254 | - if ($markComplete) { |
|
| 255 | - $taskItemProps[$this->proptags["reset_reminder"]] = false; |
|
| 256 | - $taskItemProps[$this->proptags["reminder"]] = false; |
|
| 257 | - $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"]; |
|
| 258 | - |
|
| 259 | - unset($this->action[$this->proptags['datecompleted']]); |
|
| 260 | - } |
|
| 261 | - |
|
| 262 | - // Recurrence ends for this item |
|
| 263 | - $taskItemProps[$this->proptags["dead_occurrence"]] = true; |
|
| 264 | - $taskItemProps[$this->proptags["task_f_creator"]] = true; |
|
| 265 | - |
|
| 266 | - // OL props |
|
| 267 | - $taskItemProps[$this->proptags["side_effects"]] = 1296; |
|
| 268 | - $taskItemProps[$this->proptags["icon_index"]] = 1280; |
|
| 269 | - |
|
| 270 | - // Copy recipients |
|
| 271 | - $recipienttable = mapi_message_getrecipienttable($this->message); |
|
| 272 | - $recipients = mapi_table_queryallrows($recipienttable, [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]); |
|
| 273 | - |
|
| 274 | - $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage); |
|
| 275 | - $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]); |
|
| 276 | - foreach ($copy_to_recipientRows as $recipient) { |
|
| 277 | - mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]); |
|
| 278 | - } |
|
| 279 | - mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients); |
|
| 280 | - |
|
| 281 | - // Copy attachments |
|
| 282 | - $attachmentTable = mapi_message_getattachmenttable($this->message); |
|
| 283 | - if ($attachmentTable) { |
|
| 284 | - $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
|
| 285 | - |
|
| 286 | - foreach ($attachments as $attach_props) { |
|
| 287 | - $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]); |
|
| 288 | - $attach_newResourceMsg = mapi_message_createattach($newMessage); |
|
| 289 | - |
|
| 290 | - mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
|
| 291 | - mapi_savechanges($attach_newResourceMsg); |
|
| 292 | - } |
|
| 293 | - } |
|
| 294 | - |
|
| 295 | - mapi_setprops($newMessage, $taskItemProps); |
|
| 296 | - mapi_savechanges($newMessage); |
|
| 297 | - |
|
| 298 | - // Update body of original message |
|
| 299 | - $msgbody = mapi_openproperty($this->message, PR_BODY); |
|
| 300 | - $msgbody = trim($msgbody, "\0"); |
|
| 301 | - $separator = "------------\r\n"; |
|
| 302 | - |
|
| 303 | - if (!empty($msgbody) && strrpos($msgbody, $separator) === false) { |
|
| 304 | - $msgbody = $separator . $msgbody; |
|
| 305 | - $stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0); |
|
| 306 | - mapi_stream_setsize($stream, strlen($msgbody)); |
|
| 307 | - mapi_stream_write($stream, $msgbody); |
|
| 308 | - mapi_stream_commit($stream); |
|
| 309 | - } |
|
| 310 | - |
|
| 311 | - // We need these properties to notify client |
|
| 312 | - return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]); |
|
| 313 | - } |
|
| 314 | - |
|
| 315 | - /** |
|
| 316 | - * processOccurrenceItem, adds an item to a list of occurrences, but only if the |
|
| 317 | - * resulting occurrence starts or ends in the interval <$start, $end>. |
|
| 318 | - * |
|
| 319 | - * @param array $items reference to the array to be added to |
|
| 320 | - * @param date $start start of timeframe in GMT TIME |
|
| 321 | - * @param date $end end of timeframe in GMT TIME |
|
| 322 | - * @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE |
|
| 323 | - * @param mixed $now |
|
| 324 | - */ |
|
| 325 | - public function processOccurrenceItem(&$items, $start, $end, $now) { |
|
| 326 | - if ($now > $start) { |
|
| 327 | - $newItem = []; |
|
| 328 | - $newItem[$this->proptags['startdate']] = $now; |
|
| 329 | - |
|
| 330 | - // If startdate and enddate are set on task, then slide enddate according to duration |
|
| 331 | - if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) { |
|
| 332 | - $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]); |
|
| 333 | - } |
|
| 334 | - else { |
|
| 335 | - $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']]; |
|
| 336 | - } |
|
| 337 | - |
|
| 338 | - $items[] = $newItem; |
|
| 339 | - } |
|
| 340 | - } |
|
| 341 | - |
|
| 342 | - /** |
|
| 343 | - * Function which marks existing occurrence to 'Complete'. |
|
| 344 | - * |
|
| 345 | - *@param array $recur array action from client |
|
| 346 | - * |
|
| 347 | - *@return array of properties of regenerated task else false |
|
| 348 | - */ |
|
| 349 | - public function markOccurrenceComplete(&$recur) { |
|
| 350 | - // Fix timezone object |
|
| 351 | - $this->tz = false; |
|
| 352 | - $this->action = &$recur; |
|
| 353 | - $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false; |
|
| 354 | - |
|
| 355 | - if (!$dead_occurrence) { |
|
| 356 | - return $this->moveToNextOccurrence(); |
|
| 357 | - } |
|
| 358 | - |
|
| 359 | - return false; |
|
| 360 | - } |
|
| 361 | - |
|
| 362 | - /** |
|
| 363 | - * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete. |
|
| 364 | - * |
|
| 365 | - *@param array $nextOccurrence properties of next occurrence |
|
| 366 | - */ |
|
| 367 | - public function setReminder($nextOccurrence) { |
|
| 368 | - $props = []; |
|
| 369 | - if ($nextOccurrence) { |
|
| 370 | - // Check if reminder is reset. Default is 'false' |
|
| 371 | - $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false; |
|
| 372 | - $reminder = $this->messageprops[$this->proptags['reminder']]; |
|
| 373 | - |
|
| 374 | - // Either reminder was already set OR reminder was set but was dismissed bty user |
|
| 375 | - if ($reminder || $reset_reminder) { |
|
| 376 | - // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate |
|
| 377 | - $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0; |
|
| 378 | - $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0; |
|
| 379 | - $reminder_difference = $reminder_difference - $reminder_time; |
|
| 380 | - |
|
| 381 | - // Apply duration to next calculated duedate |
|
| 382 | - $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference; |
|
| 383 | - |
|
| 384 | - $props[$this->proptags['reminder_time']] = $next_reminder_time; |
|
| 385 | - $props[$this->proptags['flagdueby']] = $next_reminder_time; |
|
| 386 | - $this->action['reminder'] = $props[$this->proptags['reminder']] = true; |
|
| 387 | - } |
|
| 388 | - } |
|
| 389 | - else { |
|
| 390 | - // Didn't get next occurrence, probably this is the last occurrence |
|
| 391 | - $props[$this->proptags['reminder']] = false; |
|
| 392 | - $props[$this->proptags['reset_reminder']] = false; |
|
| 393 | - } |
|
| 394 | - |
|
| 395 | - if (!empty($props)) { |
|
| 396 | - mapi_setprops($this->message, $props); |
|
| 397 | - } |
|
| 398 | - } |
|
| 399 | - |
|
| 400 | - /** |
|
| 401 | - * Function which recurring task to next occurrence. |
|
| 402 | - * It simply doesn't regenerate task. |
|
| 403 | - * |
|
| 404 | - * @param array $action |
|
| 405 | - */ |
|
| 406 | - public function deleteOccurrence($action) { |
|
| 407 | - $this->tz = false; |
|
| 408 | - $this->action = $action; |
|
| 409 | - $result = $this->moveToNextOccurrence(); |
|
| 410 | - |
|
| 411 | - mapi_savechanges($this->message); |
|
| 412 | - |
|
| 413 | - return $result; |
|
| 414 | - } |
|
| 415 | - } |
|
| 145 | + if ((empty($this->action['startdate']) && empty($this->action['duedate'])) || |
|
| 146 | + ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) { |
|
| 147 | + $nextOccurrence = $this->getNextOccurrence(); |
|
| 148 | + $result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]); |
|
| 149 | + |
|
| 150 | + $props = []; |
|
| 151 | + if ($nextOccurrence) { |
|
| 152 | + if (!isset($this->action['deleteOccurrence'])) { |
|
| 153 | + // Create current occurrence as separate task |
|
| 154 | + $result = $this->regenerateTask($this->action['complete']); |
|
| 155 | + } |
|
| 156 | + |
|
| 157 | + // Set reminder for next occurrence |
|
| 158 | + $this->setReminder($nextOccurrence); |
|
| 159 | + |
|
| 160 | + // Update properties for next occurrence |
|
| 161 | + $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']]; |
|
| 162 | + $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']]; |
|
| 163 | + |
|
| 164 | + $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']]; |
|
| 165 | + $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']]; |
|
| 166 | + |
|
| 167 | + // If current task as been mark as 'Complete' then next occurrence should be incomplete. |
|
| 168 | + if (isset($this->action['complete']) && $this->action['complete'] == 1) { |
|
| 169 | + $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted; |
|
| 170 | + $this->action['complete'] = $props[$this->proptags["complete"]] = false; |
|
| 171 | + $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0; |
|
| 172 | + } |
|
| 173 | + |
|
| 174 | + $props[$this->proptags["dead_occurrence"]] = false; |
|
| 175 | + } |
|
| 176 | + else { |
|
| 177 | + if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) { |
|
| 178 | + return false; |
|
| 179 | + } |
|
| 180 | + |
|
| 181 | + // Didn't get next occurrence, probably this is the last one, so recurrence ends here |
|
| 182 | + $props[$this->proptags["dead_occurrence"]] = true; |
|
| 183 | + $props[$this->proptags["datecompleted"]] = $this->action['datecompleted']; |
|
| 184 | + $props[$this->proptags["task_f_creator"]] = true; |
|
| 185 | + |
|
| 186 | + // OL props |
|
| 187 | + $props[$this->proptags["side_effects"]] = 1296; |
|
| 188 | + $props[$this->proptags["icon_index"]] = 1280; |
|
| 189 | + } |
|
| 190 | + |
|
| 191 | + mapi_setprops($this->message, $props); |
|
| 192 | + } |
|
| 193 | + |
|
| 194 | + return $result; |
|
| 195 | + } |
|
| 196 | + |
|
| 197 | + /** |
|
| 198 | + * Function which return properties of next occurrence. |
|
| 199 | + * |
|
| 200 | + *@return array startdate/enddate of next occurrence |
|
| 201 | + */ |
|
| 202 | + public function getNextOccurrence() { |
|
| 203 | + if ($this->recur) { |
|
| 204 | + $items = []; |
|
| 205 | + |
|
| 206 | + // @TODO: fix start of range |
|
| 207 | + $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start']; |
|
| 208 | + $dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]); |
|
| 209 | + |
|
| 210 | + // Fix recur object |
|
| 211 | + $this->recur['startocc'] = 0; |
|
| 212 | + $this->recur['endocc'] = 0; |
|
| 213 | + |
|
| 214 | + // Retrieve next occurrence |
|
| 215 | + $items = $this->getItems($start, $dayend, 1); |
|
| 216 | + |
|
| 217 | + return !empty($items) ? $items[0] : false; |
|
| 218 | + } |
|
| 219 | + } |
|
| 220 | + |
|
| 221 | + /** |
|
| 222 | + * Function which clones current occurrence and sets appropriate properties. |
|
| 223 | + * The original recurring item is moved to next occurrence. |
|
| 224 | + * |
|
| 225 | + *@param bool $markComplete true if existing occurrence has to be mark complete else false |
|
| 226 | + */ |
|
| 227 | + public function regenerateTask($markComplete) { |
|
| 228 | + // Get all properties |
|
| 229 | + $taskItemProps = mapi_getprops($this->message); |
|
| 230 | + |
|
| 231 | + if (isset($this->action["subject"])) { |
|
| 232 | + $taskItemProps[$this->proptags["subject"]] = $this->action["subject"]; |
|
| 233 | + } |
|
| 234 | + if (isset($this->action["importance"])) { |
|
| 235 | + $taskItemProps[$this->proptags["importance"]] = $this->action["importance"]; |
|
| 236 | + } |
|
| 237 | + if (isset($this->action["startdate"])) { |
|
| 238 | + $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"]; |
|
| 239 | + $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"]; |
|
| 240 | + } |
|
| 241 | + if (isset($this->action["duedate"])) { |
|
| 242 | + $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"]; |
|
| 243 | + $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"]; |
|
| 244 | + } |
|
| 245 | + |
|
| 246 | + $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]); |
|
| 247 | + $newMessage = mapi_folder_createmessage($folder); |
|
| 248 | + |
|
| 249 | + $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted; |
|
| 250 | + $taskItemProps[$this->proptags["complete"]] = $markComplete; |
|
| 251 | + $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0; |
|
| 252 | + |
|
| 253 | + // This occurrence has been marked as 'Complete' so disable reminder |
|
| 254 | + if ($markComplete) { |
|
| 255 | + $taskItemProps[$this->proptags["reset_reminder"]] = false; |
|
| 256 | + $taskItemProps[$this->proptags["reminder"]] = false; |
|
| 257 | + $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"]; |
|
| 258 | + |
|
| 259 | + unset($this->action[$this->proptags['datecompleted']]); |
|
| 260 | + } |
|
| 261 | + |
|
| 262 | + // Recurrence ends for this item |
|
| 263 | + $taskItemProps[$this->proptags["dead_occurrence"]] = true; |
|
| 264 | + $taskItemProps[$this->proptags["task_f_creator"]] = true; |
|
| 265 | + |
|
| 266 | + // OL props |
|
| 267 | + $taskItemProps[$this->proptags["side_effects"]] = 1296; |
|
| 268 | + $taskItemProps[$this->proptags["icon_index"]] = 1280; |
|
| 269 | + |
|
| 270 | + // Copy recipients |
|
| 271 | + $recipienttable = mapi_message_getrecipienttable($this->message); |
|
| 272 | + $recipients = mapi_table_queryallrows($recipienttable, [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]); |
|
| 273 | + |
|
| 274 | + $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage); |
|
| 275 | + $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]); |
|
| 276 | + foreach ($copy_to_recipientRows as $recipient) { |
|
| 277 | + mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]); |
|
| 278 | + } |
|
| 279 | + mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients); |
|
| 280 | + |
|
| 281 | + // Copy attachments |
|
| 282 | + $attachmentTable = mapi_message_getattachmenttable($this->message); |
|
| 283 | + if ($attachmentTable) { |
|
| 284 | + $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
|
| 285 | + |
|
| 286 | + foreach ($attachments as $attach_props) { |
|
| 287 | + $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]); |
|
| 288 | + $attach_newResourceMsg = mapi_message_createattach($newMessage); |
|
| 289 | + |
|
| 290 | + mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
|
| 291 | + mapi_savechanges($attach_newResourceMsg); |
|
| 292 | + } |
|
| 293 | + } |
|
| 294 | + |
|
| 295 | + mapi_setprops($newMessage, $taskItemProps); |
|
| 296 | + mapi_savechanges($newMessage); |
|
| 297 | + |
|
| 298 | + // Update body of original message |
|
| 299 | + $msgbody = mapi_openproperty($this->message, PR_BODY); |
|
| 300 | + $msgbody = trim($msgbody, "\0"); |
|
| 301 | + $separator = "------------\r\n"; |
|
| 302 | + |
|
| 303 | + if (!empty($msgbody) && strrpos($msgbody, $separator) === false) { |
|
| 304 | + $msgbody = $separator . $msgbody; |
|
| 305 | + $stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0); |
|
| 306 | + mapi_stream_setsize($stream, strlen($msgbody)); |
|
| 307 | + mapi_stream_write($stream, $msgbody); |
|
| 308 | + mapi_stream_commit($stream); |
|
| 309 | + } |
|
| 310 | + |
|
| 311 | + // We need these properties to notify client |
|
| 312 | + return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]); |
|
| 313 | + } |
|
| 314 | + |
|
| 315 | + /** |
|
| 316 | + * processOccurrenceItem, adds an item to a list of occurrences, but only if the |
|
| 317 | + * resulting occurrence starts or ends in the interval <$start, $end>. |
|
| 318 | + * |
|
| 319 | + * @param array $items reference to the array to be added to |
|
| 320 | + * @param date $start start of timeframe in GMT TIME |
|
| 321 | + * @param date $end end of timeframe in GMT TIME |
|
| 322 | + * @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE |
|
| 323 | + * @param mixed $now |
|
| 324 | + */ |
|
| 325 | + public function processOccurrenceItem(&$items, $start, $end, $now) { |
|
| 326 | + if ($now > $start) { |
|
| 327 | + $newItem = []; |
|
| 328 | + $newItem[$this->proptags['startdate']] = $now; |
|
| 329 | + |
|
| 330 | + // If startdate and enddate are set on task, then slide enddate according to duration |
|
| 331 | + if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) { |
|
| 332 | + $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]); |
|
| 333 | + } |
|
| 334 | + else { |
|
| 335 | + $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']]; |
|
| 336 | + } |
|
| 337 | + |
|
| 338 | + $items[] = $newItem; |
|
| 339 | + } |
|
| 340 | + } |
|
| 341 | + |
|
| 342 | + /** |
|
| 343 | + * Function which marks existing occurrence to 'Complete'. |
|
| 344 | + * |
|
| 345 | + *@param array $recur array action from client |
|
| 346 | + * |
|
| 347 | + *@return array of properties of regenerated task else false |
|
| 348 | + */ |
|
| 349 | + public function markOccurrenceComplete(&$recur) { |
|
| 350 | + // Fix timezone object |
|
| 351 | + $this->tz = false; |
|
| 352 | + $this->action = &$recur; |
|
| 353 | + $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false; |
|
| 354 | + |
|
| 355 | + if (!$dead_occurrence) { |
|
| 356 | + return $this->moveToNextOccurrence(); |
|
| 357 | + } |
|
| 358 | + |
|
| 359 | + return false; |
|
| 360 | + } |
|
| 361 | + |
|
| 362 | + /** |
|
| 363 | + * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete. |
|
| 364 | + * |
|
| 365 | + *@param array $nextOccurrence properties of next occurrence |
|
| 366 | + */ |
|
| 367 | + public function setReminder($nextOccurrence) { |
|
| 368 | + $props = []; |
|
| 369 | + if ($nextOccurrence) { |
|
| 370 | + // Check if reminder is reset. Default is 'false' |
|
| 371 | + $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false; |
|
| 372 | + $reminder = $this->messageprops[$this->proptags['reminder']]; |
|
| 373 | + |
|
| 374 | + // Either reminder was already set OR reminder was set but was dismissed bty user |
|
| 375 | + if ($reminder || $reset_reminder) { |
|
| 376 | + // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate |
|
| 377 | + $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0; |
|
| 378 | + $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0; |
|
| 379 | + $reminder_difference = $reminder_difference - $reminder_time; |
|
| 380 | + |
|
| 381 | + // Apply duration to next calculated duedate |
|
| 382 | + $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference; |
|
| 383 | + |
|
| 384 | + $props[$this->proptags['reminder_time']] = $next_reminder_time; |
|
| 385 | + $props[$this->proptags['flagdueby']] = $next_reminder_time; |
|
| 386 | + $this->action['reminder'] = $props[$this->proptags['reminder']] = true; |
|
| 387 | + } |
|
| 388 | + } |
|
| 389 | + else { |
|
| 390 | + // Didn't get next occurrence, probably this is the last occurrence |
|
| 391 | + $props[$this->proptags['reminder']] = false; |
|
| 392 | + $props[$this->proptags['reset_reminder']] = false; |
|
| 393 | + } |
|
| 394 | + |
|
| 395 | + if (!empty($props)) { |
|
| 396 | + mapi_setprops($this->message, $props); |
|
| 397 | + } |
|
| 398 | + } |
|
| 399 | + |
|
| 400 | + /** |
|
| 401 | + * Function which recurring task to next occurrence. |
|
| 402 | + * It simply doesn't regenerate task. |
|
| 403 | + * |
|
| 404 | + * @param array $action |
|
| 405 | + */ |
|
| 406 | + public function deleteOccurrence($action) { |
|
| 407 | + $this->tz = false; |
|
| 408 | + $this->action = $action; |
|
| 409 | + $result = $this->moveToNextOccurrence(); |
|
| 410 | + |
|
| 411 | + mapi_savechanges($this->message); |
|
| 412 | + |
|
| 413 | + return $result; |
|
| 414 | + } |
|
| 415 | + } |
|
@@ -284,7 +284,7 @@ discard block |
||
| 284 | 284 | $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
| 285 | 285 | |
| 286 | 286 | foreach ($attachments as $attach_props) { |
| 287 | - $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]); |
|
| 287 | + $attach_old = mapi_message_openattach($this->message, (int)$attach_props[PR_ATTACH_NUM]); |
|
| 288 | 288 | $attach_newResourceMsg = mapi_message_createattach($newMessage); |
| 289 | 289 | |
| 290 | 290 | mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
@@ -301,7 +301,7 @@ discard block |
||
| 301 | 301 | $separator = "------------\r\n"; |
| 302 | 302 | |
| 303 | 303 | if (!empty($msgbody) && strrpos($msgbody, $separator) === false) { |
| 304 | - $msgbody = $separator . $msgbody; |
|
| 304 | + $msgbody = $separator.$msgbody; |
|
| 305 | 305 | $stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, STGM_TRANSACTED, 0); |
| 306 | 306 | mapi_stream_setsize($stream, strlen($msgbody)); |
| 307 | 307 | mapi_stream_write($stream, $msgbody); |
@@ -172,8 +172,7 @@ discard block |
||
| 172 | 172 | } |
| 173 | 173 | |
| 174 | 174 | $props[$this->proptags["dead_occurrence"]] = false; |
| 175 | - } |
|
| 176 | - else { |
|
| 175 | + } else { |
|
| 177 | 176 | if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) { |
| 178 | 177 | return false; |
| 179 | 178 | } |
@@ -330,8 +329,7 @@ discard block |
||
| 330 | 329 | // If startdate and enddate are set on task, then slide enddate according to duration |
| 331 | 330 | if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) { |
| 332 | 331 | $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]); |
| 333 | - } |
|
| 334 | - else { |
|
| 332 | + } else { |
|
| 335 | 333 | $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']]; |
| 336 | 334 | } |
| 337 | 335 | |
@@ -385,8 +383,7 @@ discard block |
||
| 385 | 383 | $props[$this->proptags['flagdueby']] = $next_reminder_time; |
| 386 | 384 | $this->action['reminder'] = $props[$this->proptags['reminder']] = true; |
| 387 | 385 | } |
| 388 | - } |
|
| 389 | - else { |
|
| 386 | + } else { |
|
| 390 | 387 | // Didn't get next occurrence, probably this is the last occurrence |
| 391 | 388 | $props[$this->proptags['reminder']] = false; |
| 392 | 389 | $props[$this->proptags['reset_reminder']] = false; |
@@ -27,7 +27,7 @@ discard block |
||
| 27 | 27 | * @param mixed $code |
| 28 | 28 | */ |
| 29 | 29 | function make_mapi_e($code) { |
| 30 | - return (int) mapi_make_scode(1, $code); |
|
| 30 | + return (int) mapi_make_scode(1, $code); |
|
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | /** |
@@ -36,7 +36,7 @@ discard block |
||
| 36 | 36 | * @param mixed $code |
| 37 | 37 | */ |
| 38 | 38 | function make_mapi_s($code) { |
| 39 | - return (int) mapi_make_scode(0, $code); |
|
| 39 | + return (int) mapi_make_scode(0, $code); |
|
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | /* From mapicode.h */ |
@@ -27,7 +27,7 @@ discard block |
||
| 27 | 27 | * @param mixed $code |
| 28 | 28 | */ |
| 29 | 29 | function make_mapi_e($code) { |
| 30 | - return (int) mapi_make_scode(1, $code); |
|
| 30 | + return (int)mapi_make_scode(1, $code); |
|
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | /** |
@@ -36,7 +36,7 @@ discard block |
||
| 36 | 36 | * @param mixed $code |
| 37 | 37 | */ |
| 38 | 38 | function make_mapi_s($code) { |
| 39 | - return (int) mapi_make_scode(0, $code); |
|
| 39 | + return (int)mapi_make_scode(0, $code); |
|
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | /* From mapicode.h */ |
@@ -5,11 +5,11 @@ discard block |
||
| 5 | 5 | * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | - /** |
|
| 9 | - * Recurrence. |
|
| 10 | - */ |
|
| 11 | - class Recurrence extends BaseRecurrence { |
|
| 12 | - /* |
|
| 8 | + /** |
|
| 9 | + * Recurrence. |
|
| 10 | + */ |
|
| 11 | + class Recurrence extends BaseRecurrence { |
|
| 12 | + /* |
|
| 13 | 13 | * ABOUT TIMEZONES |
| 14 | 14 | * |
| 15 | 15 | * Timezones are rather complicated here so here are some rules to think about: |
@@ -21,1279 +21,1279 @@ discard block |
||
| 21 | 21 | * always in LOCAL time. |
| 22 | 22 | */ |
| 23 | 23 | |
| 24 | - // All properties for a recipient that are interesting |
|
| 25 | - public $recipprops = [ |
|
| 26 | - PR_ENTRYID, |
|
| 27 | - PR_SEARCH_KEY, |
|
| 28 | - PR_DISPLAY_NAME, |
|
| 29 | - PR_EMAIL_ADDRESS, |
|
| 30 | - PR_RECIPIENT_ENTRYID, |
|
| 31 | - PR_RECIPIENT_TYPE, |
|
| 32 | - PR_SEND_INTERNET_ENCODING, |
|
| 33 | - PR_SEND_RICH_INFO, |
|
| 34 | - PR_RECIPIENT_DISPLAY_NAME, |
|
| 35 | - PR_ADDRTYPE, |
|
| 36 | - PR_DISPLAY_TYPE, |
|
| 37 | - PR_DISPLAY_TYPE_EX, |
|
| 38 | - PR_RECIPIENT_TRACKSTATUS, |
|
| 39 | - PR_RECIPIENT_TRACKSTATUS_TIME, |
|
| 40 | - PR_RECIPIENT_FLAGS, |
|
| 41 | - PR_ROWID, |
|
| 42 | - ]; |
|
| 43 | - |
|
| 44 | - /** |
|
| 45 | - * Constructor. |
|
| 46 | - * |
|
| 47 | - * @param resource $store MAPI Message Store Object |
|
| 48 | - * @param resource $message the MAPI (appointment) message |
|
| 49 | - * @param array $proptags an associative array of protags and their values |
|
| 50 | - */ |
|
| 51 | - public function __construct($store, $message, $proptags = []) { |
|
| 52 | - if ($proptags) { |
|
| 53 | - $this->proptags = $proptags; |
|
| 54 | - } |
|
| 55 | - else { |
|
| 56 | - $properties = []; |
|
| 57 | - $properties["entryid"] = PR_ENTRYID; |
|
| 58 | - $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
|
| 59 | - $properties["message_class"] = PR_MESSAGE_CLASS; |
|
| 60 | - $properties["icon_index"] = PR_ICON_INDEX; |
|
| 61 | - $properties["subject"] = PR_SUBJECT; |
|
| 62 | - $properties["display_to"] = PR_DISPLAY_TO; |
|
| 63 | - $properties["importance"] = PR_IMPORTANCE; |
|
| 64 | - $properties["sensitivity"] = PR_SENSITIVITY; |
|
| 65 | - $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d"; |
|
| 66 | - $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e"; |
|
| 67 | - $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223"; |
|
| 68 | - $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216"; |
|
| 69 | - $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205"; |
|
| 70 | - $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214"; |
|
| 71 | - $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215"; |
|
| 72 | - $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; |
|
| 73 | - $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217"; |
|
| 74 | - $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235"; |
|
| 75 | - $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236"; |
|
| 76 | - $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232"; |
|
| 77 | - $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208"; |
|
| 78 | - $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213"; |
|
| 79 | - $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218"; |
|
| 80 | - $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 81 | - $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 82 | - $properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231"; |
|
| 83 | - $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
|
| 84 | - $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
|
| 85 | - $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 86 | - $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502"; |
|
| 87 | - $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
|
| 88 | - $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
|
| 89 | - $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228"; |
|
| 90 | - $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233"; |
|
| 91 | - $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234"; |
|
| 92 | - $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560"; |
|
| 93 | - $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510"; |
|
| 94 | - $properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514"; |
|
| 95 | - |
|
| 96 | - $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 97 | - } |
|
| 98 | - |
|
| 99 | - parent::__construct($store, $message); |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - /** |
|
| 103 | - * Create an exception. |
|
| 104 | - * |
|
| 105 | - * @param array $exception_props the exception properties (same properties as normal recurring items) |
|
| 106 | - * @param date $base_date the base date of the exception (LOCAL time of non-exception occurrence) |
|
| 107 | - * @param bool $delete true - delete occurrence, false - create new exception or modify existing |
|
| 108 | - * @param array $exception_recips true - delete occurrence, false - create new exception or modify existing |
|
| 109 | - * @param mapi_message $copy_attach_from mapi message from which attachments should be copied |
|
| 110 | - */ |
|
| 111 | - public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false) { |
|
| 112 | - $baseday = $this->dayStartOf($base_date); |
|
| 113 | - $basetime = $baseday + $this->recur["startocc"] * 60; |
|
| 114 | - |
|
| 115 | - // Remove any pre-existing exception on this base date |
|
| 116 | - if ($this->isException($baseday)) { |
|
| 117 | - $this->deleteException($baseday); // note that deleting an exception is different from creating a deleted exception (deleting an occurrence). |
|
| 118 | - } |
|
| 119 | - |
|
| 120 | - if (!$delete) { |
|
| 121 | - if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) { |
|
| 122 | - return false; |
|
| 123 | - } |
|
| 124 | - // Properties in the attachment are the properties of the base object, plus $exception_props plus the base date |
|
| 125 | - foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) { |
|
| 126 | - if (isset($this->messageprops[$this->proptags[$propname]])) { |
|
| 127 | - $props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]]; |
|
| 128 | - } |
|
| 129 | - } |
|
| 130 | - |
|
| 131 | - $props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"; |
|
| 132 | - unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]); |
|
| 133 | - |
|
| 134 | - $props = $exception_props + $props; |
|
| 135 | - |
|
| 136 | - // Basedate in the exception attachment is the GMT time at which the original occurrence would have been |
|
| 137 | - $props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime); |
|
| 138 | - |
|
| 139 | - if (!isset($exception_props[$this->proptags["startdate"]])) { |
|
| 140 | - $props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date); |
|
| 141 | - } |
|
| 142 | - |
|
| 143 | - if (!isset($exception_props[$this->proptags["duedate"]])) { |
|
| 144 | - $props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date); |
|
| 145 | - } |
|
| 146 | - |
|
| 147 | - // synchronize commonstart/commonend with startdate/duedate |
|
| 148 | - if (isset($props[$this->proptags["startdate"]])) { |
|
| 149 | - $props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]]; |
|
| 150 | - } |
|
| 151 | - |
|
| 152 | - if (isset($props[$this->proptags["duedate"]])) { |
|
| 153 | - $props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]]; |
|
| 154 | - } |
|
| 155 | - |
|
| 156 | - // Save the data into an attachment |
|
| 157 | - $this->createExceptionAttachment($props, $exception_recips, $copy_attach_from); |
|
| 158 | - |
|
| 159 | - $changed_item = []; |
|
| 160 | - |
|
| 161 | - $changed_item["basedate"] = $baseday; |
|
| 162 | - $changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]); |
|
| 163 | - $changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]); |
|
| 164 | - |
|
| 165 | - if (array_key_exists($this->proptags["subject"], $exception_props)) { |
|
| 166 | - $changed_item["subject"] = $exception_props[$this->proptags["subject"]]; |
|
| 167 | - } |
|
| 168 | - |
|
| 169 | - if (array_key_exists($this->proptags["location"], $exception_props)) { |
|
| 170 | - $changed_item["location"] = $exception_props[$this->proptags["location"]]; |
|
| 171 | - } |
|
| 172 | - |
|
| 173 | - if (array_key_exists($this->proptags["label"], $exception_props)) { |
|
| 174 | - $changed_item["label"] = $exception_props[$this->proptags["label"]]; |
|
| 175 | - } |
|
| 176 | - |
|
| 177 | - if (array_key_exists($this->proptags["reminder"], $exception_props)) { |
|
| 178 | - $changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]]; |
|
| 179 | - } |
|
| 180 | - |
|
| 181 | - if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) { |
|
| 182 | - $changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]]; |
|
| 183 | - } |
|
| 184 | - |
|
| 185 | - if (array_key_exists($this->proptags["alldayevent"], $exception_props)) { |
|
| 186 | - $changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]]; |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - if (array_key_exists($this->proptags["busystatus"], $exception_props)) { |
|
| 190 | - $changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]]; |
|
| 191 | - } |
|
| 192 | - |
|
| 193 | - // Add the changed occurrence to the list |
|
| 194 | - array_push($this->recur["changed_occurrences"], $changed_item); |
|
| 195 | - } |
|
| 196 | - else { |
|
| 197 | - // Delete the occurrence by placing it in the deleted occurrences list |
|
| 198 | - array_push($this->recur["deleted_occurrences"], $baseday); |
|
| 199 | - } |
|
| 200 | - |
|
| 201 | - // Turn on hideattachments, because the attachments in this item are the exceptions |
|
| 202 | - mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]); |
|
| 203 | - |
|
| 204 | - // Save recurrence data to message |
|
| 205 | - $this->saveRecurrence(); |
|
| 206 | - |
|
| 207 | - return true; |
|
| 208 | - } |
|
| 209 | - |
|
| 210 | - /** |
|
| 211 | - * Modifies an existing exception, but only updates the given properties |
|
| 212 | - * NOTE: You can't remove properties from an exception, only add new ones. |
|
| 213 | - * |
|
| 214 | - * @param mixed $exception_props |
|
| 215 | - * @param mixed $base_date |
|
| 216 | - * @param mixed $exception_recips |
|
| 217 | - * @param mixed $copy_attach_from |
|
| 218 | - */ |
|
| 219 | - public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false) { |
|
| 220 | - if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) { |
|
| 221 | - return false; |
|
| 222 | - } |
|
| 223 | - |
|
| 224 | - $baseday = $this->dayStartOf($base_date); |
|
| 225 | - $extomodify = false; |
|
| 226 | - |
|
| 227 | - for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) { |
|
| 228 | - if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) { |
|
| 229 | - $extomodify = &$this->recur["changed_occurrences"][$i]; |
|
| 230 | - } |
|
| 231 | - } |
|
| 232 | - |
|
| 233 | - if (!$extomodify) { |
|
| 234 | - return false; |
|
| 235 | - } |
|
| 236 | - |
|
| 237 | - // remove basedate property as we want to preserve the old value |
|
| 238 | - // client will send basedate with time part as zero, so discard that value |
|
| 239 | - unset($exception_props[$this->proptags["basedate"]]); |
|
| 240 | - |
|
| 241 | - if (array_key_exists($this->proptags["startdate"], $exception_props)) { |
|
| 242 | - $extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); |
|
| 243 | - } |
|
| 244 | - |
|
| 245 | - if (array_key_exists($this->proptags["duedate"], $exception_props)) { |
|
| 246 | - $extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
|
| 247 | - } |
|
| 248 | - |
|
| 249 | - if (array_key_exists($this->proptags["subject"], $exception_props)) { |
|
| 250 | - $extomodify["subject"] = $exception_props[$this->proptags["subject"]]; |
|
| 251 | - } |
|
| 252 | - |
|
| 253 | - if (array_key_exists($this->proptags["location"], $exception_props)) { |
|
| 254 | - $extomodify["location"] = $exception_props[$this->proptags["location"]]; |
|
| 255 | - } |
|
| 256 | - |
|
| 257 | - if (array_key_exists($this->proptags["label"], $exception_props)) { |
|
| 258 | - $extomodify["label"] = $exception_props[$this->proptags["label"]]; |
|
| 259 | - } |
|
| 260 | - |
|
| 261 | - if (array_key_exists($this->proptags["reminder"], $exception_props)) { |
|
| 262 | - $extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]]; |
|
| 263 | - } |
|
| 264 | - |
|
| 265 | - if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) { |
|
| 266 | - $extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]]; |
|
| 267 | - } |
|
| 268 | - |
|
| 269 | - if (array_key_exists($this->proptags["alldayevent"], $exception_props)) { |
|
| 270 | - $extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]]; |
|
| 271 | - } |
|
| 272 | - |
|
| 273 | - if (array_key_exists($this->proptags["busystatus"], $exception_props)) { |
|
| 274 | - $extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]]; |
|
| 275 | - } |
|
| 276 | - |
|
| 277 | - $exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"; |
|
| 278 | - |
|
| 279 | - // synchronize commonstart/commonend with startdate/duedate |
|
| 280 | - if (isset($exception_props[$this->proptags["startdate"]])) { |
|
| 281 | - $exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]]; |
|
| 282 | - } |
|
| 283 | - |
|
| 284 | - if (isset($exception_props[$this->proptags["duedate"]])) { |
|
| 285 | - $exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]]; |
|
| 286 | - } |
|
| 287 | - |
|
| 288 | - $attach = $this->getExceptionAttachment($baseday); |
|
| 289 | - if (!$attach) { |
|
| 290 | - if ($copy_attach_from) { |
|
| 291 | - $this->deleteExceptionAttachment($base_date); |
|
| 292 | - $this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from); |
|
| 293 | - } |
|
| 294 | - else { |
|
| 295 | - $this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from); |
|
| 296 | - } |
|
| 297 | - } |
|
| 298 | - else { |
|
| 299 | - $message = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 300 | - |
|
| 301 | - // Set exception properties on embedded message and save |
|
| 302 | - mapi_setprops($message, $exception_props); |
|
| 303 | - $this->setExceptionRecipients($message, $exception_recips, false); |
|
| 304 | - mapi_savechanges($message); |
|
| 305 | - |
|
| 306 | - // If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME' |
|
| 307 | - // on the attachment which holds the embedded msg and save everything. |
|
| 308 | - $props = []; |
|
| 309 | - if (isset($exception_props[$this->proptags["startdate"]])) { |
|
| 310 | - $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); |
|
| 311 | - } |
|
| 312 | - if (isset($exception_props[$this->proptags["duedate"]])) { |
|
| 313 | - $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
|
| 314 | - } |
|
| 315 | - if (!empty($props)) { |
|
| 316 | - mapi_setprops($attach, $props); |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - mapi_savechanges($attach); |
|
| 320 | - } |
|
| 321 | - |
|
| 322 | - // Save recurrence data to message |
|
| 323 | - $this->saveRecurrence(); |
|
| 324 | - |
|
| 325 | - return true; |
|
| 326 | - } |
|
| 327 | - |
|
| 328 | - // Checks to see if the following is true: |
|
| 329 | - // 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration) |
|
| 330 | - // 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!) |
|
| 331 | - // |
|
| 332 | - // Both $basedate and $start are in LOCAL time |
|
| 333 | - public function isValidExceptionDate($basedate, $start) { |
|
| 334 | - // The way we do this is to look at the days that we're 'moving' the item in the exception. Each |
|
| 335 | - // of these days may only contain the item that we're modifying. Any other item violates the rules. |
|
| 336 | - |
|
| 337 | - if ($this->isException($basedate)) { |
|
| 338 | - // If we're modifying an exception, we want to look at the days that we're 'moving' compared to where |
|
| 339 | - // the exception used to be. |
|
| 340 | - $oldexception = $this->getChangeException($basedate); |
|
| 341 | - $prevday = $this->dayStartOf($oldexception["start"]); |
|
| 342 | - } |
|
| 343 | - else { |
|
| 344 | - // If its a new exception, we want to look at the original placement of this item. |
|
| 345 | - $prevday = $basedate; |
|
| 346 | - } |
|
| 347 | - |
|
| 348 | - $startday = $this->dayStartOf($start); |
|
| 349 | - |
|
| 350 | - // Get all the occurrences on the days between the basedate (may be reversed) |
|
| 351 | - if ($prevday < $startday) { |
|
| 352 | - $items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60)); |
|
| 353 | - } |
|
| 354 | - else { |
|
| 355 | - $items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60)); |
|
| 356 | - } |
|
| 357 | - |
|
| 358 | - // There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range, |
|
| 359 | - // then we abort the change, since one of the rules has been violated. |
|
| 360 | - return count($items) == 1; |
|
| 361 | - } |
|
| 362 | - |
|
| 363 | - /** |
|
| 364 | - * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:. |
|
| 365 | - * |
|
| 366 | - * Both must be true: |
|
| 367 | - * - reminder time of this item is not before the starttime of the previous recurring item |
|
| 368 | - * - reminder time of the next item is not before the starttime of this item |
|
| 369 | - * |
|
| 370 | - * @param date $basedate the base date of the exception (LOCAL time of non-exception occurrence) |
|
| 371 | - * @param string $reminderminutes reminder minutes which is set of the item |
|
| 372 | - * @param date $startdate the startdate of the selected item |
|
| 373 | - * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE) |
|
| 374 | - */ |
|
| 375 | - public function isValidReminderTime($basedate, $reminderminutes, $startdate) { |
|
| 376 | - // get all occurrence items before the seleceted items occurrence starttime |
|
| 377 | - $occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate)); |
|
| 378 | - |
|
| 379 | - if (!empty($occitems)) { |
|
| 380 | - // as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems . |
|
| 381 | - $previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]]; |
|
| 382 | - |
|
| 383 | - // if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed |
|
| 384 | - if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) { |
|
| 385 | - return false; |
|
| 386 | - } |
|
| 387 | - } |
|
| 388 | - |
|
| 389 | - // Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence) |
|
| 390 | - $currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true); |
|
| 391 | - |
|
| 392 | - // If there are another two occurrences, then the first is the current occurrence, and the one after that |
|
| 393 | - // is the next occurrence. |
|
| 394 | - if (count($currentOcc) > 1) { |
|
| 395 | - $next = $currentOcc[1]; |
|
| 396 | - // Get reminder time of the next occurrence. |
|
| 397 | - $nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60); |
|
| 398 | - // If the reminder time of the next item is before the start of this item, then that's not allowed |
|
| 399 | - if ($nextOccReminderTime <= $startdate) { |
|
| 400 | - return false; |
|
| 401 | - } |
|
| 402 | - } |
|
| 403 | - |
|
| 404 | - // All was ok |
|
| 405 | - return true; |
|
| 406 | - } |
|
| 407 | - |
|
| 408 | - public function setRecurrence($tz, $recur) { |
|
| 409 | - // only reset timezone if specified |
|
| 410 | - if ($tz) { |
|
| 411 | - $this->tz = $tz; |
|
| 412 | - } |
|
| 413 | - |
|
| 414 | - $this->recur = $recur; |
|
| 415 | - |
|
| 416 | - if (!isset($this->recur["changed_occurrences"])) { |
|
| 417 | - $this->recur["changed_occurrences"] = []; |
|
| 418 | - } |
|
| 419 | - |
|
| 420 | - if (!isset($this->recur["deleted_occurrences"])) { |
|
| 421 | - $this->recur["deleted_occurrences"] = []; |
|
| 422 | - } |
|
| 423 | - |
|
| 424 | - $this->deleteAttachments(); |
|
| 425 | - $this->saveRecurrence(); |
|
| 426 | - |
|
| 427 | - // if client has not set the recurring_pattern then we should generate it and save it |
|
| 428 | - $messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]); |
|
| 429 | - if (empty($messageProps[$this->proptags["recurring_pattern"]])) { |
|
| 430 | - $this->saveRecurrencePattern(); |
|
| 431 | - } |
|
| 432 | - } |
|
| 433 | - |
|
| 434 | - // Returns the start or end time of the occurrence on the given base date. |
|
| 435 | - // This assumes that the basedate you supply is in LOCAL time |
|
| 436 | - public function getOccurrenceStart($basedate) { |
|
| 437 | - $daystart = $this->dayStartOf($basedate); |
|
| 438 | - |
|
| 439 | - return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60); |
|
| 440 | - } |
|
| 441 | - |
|
| 442 | - public function getOccurrenceEnd($basedate) { |
|
| 443 | - $daystart = $this->dayStartOf($basedate); |
|
| 444 | - |
|
| 445 | - return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60); |
|
| 446 | - } |
|
| 447 | - |
|
| 448 | - // Backwards compatible code |
|
| 449 | - public function getOccurrenceStart($basedate) { |
|
| 450 | - return $this->getOccurrenceStart($basedate); |
|
| 451 | - } |
|
| 452 | - |
|
| 453 | - public function getOccurrenceEnd($basedate) { |
|
| 454 | - return $this->getOccurrenceEnd($basedate); |
|
| 455 | - } |
|
| 456 | - |
|
| 457 | - /** |
|
| 458 | - * This function returns the next remindertime starting from $timestamp |
|
| 459 | - * When no next reminder exists, false is returned. |
|
| 460 | - * |
|
| 461 | - * Note: Before saving this new reminder time (when snoozing), you must check for |
|
| 462 | - * yourself if this reminder time is earlier than your snooze time, else |
|
| 463 | - * use your snooze time and not this reminder time. |
|
| 464 | - * |
|
| 465 | - * @param mixed $timestamp |
|
| 466 | - */ |
|
| 467 | - public function getNextReminderTime($timestamp) { |
|
| 468 | - /** |
|
| 469 | - * Get next item from now until forever, but max 1 item with reminder set |
|
| 470 | - * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT.... |
|
| 471 | - * Here for getting next 10 occurrences assuming that next here we will be able to find |
|
| 472 | - * nextreminder occurrence in 10 occureneces. |
|
| 473 | - */ |
|
| 474 | - $items = $this->getItems($timestamp, 0x7FF00000, 10, true); |
|
| 475 | - |
|
| 476 | - // Initially setting nextreminder to false so when no next reminder exists, false is returned. |
|
| 477 | - $nextreminder = false; |
|
| 478 | - /* |
|
| 24 | + // All properties for a recipient that are interesting |
|
| 25 | + public $recipprops = [ |
|
| 26 | + PR_ENTRYID, |
|
| 27 | + PR_SEARCH_KEY, |
|
| 28 | + PR_DISPLAY_NAME, |
|
| 29 | + PR_EMAIL_ADDRESS, |
|
| 30 | + PR_RECIPIENT_ENTRYID, |
|
| 31 | + PR_RECIPIENT_TYPE, |
|
| 32 | + PR_SEND_INTERNET_ENCODING, |
|
| 33 | + PR_SEND_RICH_INFO, |
|
| 34 | + PR_RECIPIENT_DISPLAY_NAME, |
|
| 35 | + PR_ADDRTYPE, |
|
| 36 | + PR_DISPLAY_TYPE, |
|
| 37 | + PR_DISPLAY_TYPE_EX, |
|
| 38 | + PR_RECIPIENT_TRACKSTATUS, |
|
| 39 | + PR_RECIPIENT_TRACKSTATUS_TIME, |
|
| 40 | + PR_RECIPIENT_FLAGS, |
|
| 41 | + PR_ROWID, |
|
| 42 | + ]; |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * Constructor. |
|
| 46 | + * |
|
| 47 | + * @param resource $store MAPI Message Store Object |
|
| 48 | + * @param resource $message the MAPI (appointment) message |
|
| 49 | + * @param array $proptags an associative array of protags and their values |
|
| 50 | + */ |
|
| 51 | + public function __construct($store, $message, $proptags = []) { |
|
| 52 | + if ($proptags) { |
|
| 53 | + $this->proptags = $proptags; |
|
| 54 | + } |
|
| 55 | + else { |
|
| 56 | + $properties = []; |
|
| 57 | + $properties["entryid"] = PR_ENTRYID; |
|
| 58 | + $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
|
| 59 | + $properties["message_class"] = PR_MESSAGE_CLASS; |
|
| 60 | + $properties["icon_index"] = PR_ICON_INDEX; |
|
| 61 | + $properties["subject"] = PR_SUBJECT; |
|
| 62 | + $properties["display_to"] = PR_DISPLAY_TO; |
|
| 63 | + $properties["importance"] = PR_IMPORTANCE; |
|
| 64 | + $properties["sensitivity"] = PR_SENSITIVITY; |
|
| 65 | + $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d"; |
|
| 66 | + $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e"; |
|
| 67 | + $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223"; |
|
| 68 | + $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216"; |
|
| 69 | + $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205"; |
|
| 70 | + $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214"; |
|
| 71 | + $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215"; |
|
| 72 | + $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; |
|
| 73 | + $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217"; |
|
| 74 | + $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235"; |
|
| 75 | + $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236"; |
|
| 76 | + $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232"; |
|
| 77 | + $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208"; |
|
| 78 | + $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213"; |
|
| 79 | + $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218"; |
|
| 80 | + $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 81 | + $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 82 | + $properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231"; |
|
| 83 | + $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
|
| 84 | + $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
|
| 85 | + $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 86 | + $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502"; |
|
| 87 | + $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
|
| 88 | + $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
|
| 89 | + $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228"; |
|
| 90 | + $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233"; |
|
| 91 | + $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234"; |
|
| 92 | + $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560"; |
|
| 93 | + $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510"; |
|
| 94 | + $properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514"; |
|
| 95 | + |
|
| 96 | + $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 97 | + } |
|
| 98 | + |
|
| 99 | + parent::__construct($store, $message); |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + /** |
|
| 103 | + * Create an exception. |
|
| 104 | + * |
|
| 105 | + * @param array $exception_props the exception properties (same properties as normal recurring items) |
|
| 106 | + * @param date $base_date the base date of the exception (LOCAL time of non-exception occurrence) |
|
| 107 | + * @param bool $delete true - delete occurrence, false - create new exception or modify existing |
|
| 108 | + * @param array $exception_recips true - delete occurrence, false - create new exception or modify existing |
|
| 109 | + * @param mapi_message $copy_attach_from mapi message from which attachments should be copied |
|
| 110 | + */ |
|
| 111 | + public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false) { |
|
| 112 | + $baseday = $this->dayStartOf($base_date); |
|
| 113 | + $basetime = $baseday + $this->recur["startocc"] * 60; |
|
| 114 | + |
|
| 115 | + // Remove any pre-existing exception on this base date |
|
| 116 | + if ($this->isException($baseday)) { |
|
| 117 | + $this->deleteException($baseday); // note that deleting an exception is different from creating a deleted exception (deleting an occurrence). |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + if (!$delete) { |
|
| 121 | + if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) { |
|
| 122 | + return false; |
|
| 123 | + } |
|
| 124 | + // Properties in the attachment are the properties of the base object, plus $exception_props plus the base date |
|
| 125 | + foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) { |
|
| 126 | + if (isset($this->messageprops[$this->proptags[$propname]])) { |
|
| 127 | + $props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]]; |
|
| 128 | + } |
|
| 129 | + } |
|
| 130 | + |
|
| 131 | + $props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"; |
|
| 132 | + unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]); |
|
| 133 | + |
|
| 134 | + $props = $exception_props + $props; |
|
| 135 | + |
|
| 136 | + // Basedate in the exception attachment is the GMT time at which the original occurrence would have been |
|
| 137 | + $props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime); |
|
| 138 | + |
|
| 139 | + if (!isset($exception_props[$this->proptags["startdate"]])) { |
|
| 140 | + $props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date); |
|
| 141 | + } |
|
| 142 | + |
|
| 143 | + if (!isset($exception_props[$this->proptags["duedate"]])) { |
|
| 144 | + $props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date); |
|
| 145 | + } |
|
| 146 | + |
|
| 147 | + // synchronize commonstart/commonend with startdate/duedate |
|
| 148 | + if (isset($props[$this->proptags["startdate"]])) { |
|
| 149 | + $props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]]; |
|
| 150 | + } |
|
| 151 | + |
|
| 152 | + if (isset($props[$this->proptags["duedate"]])) { |
|
| 153 | + $props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]]; |
|
| 154 | + } |
|
| 155 | + |
|
| 156 | + // Save the data into an attachment |
|
| 157 | + $this->createExceptionAttachment($props, $exception_recips, $copy_attach_from); |
|
| 158 | + |
|
| 159 | + $changed_item = []; |
|
| 160 | + |
|
| 161 | + $changed_item["basedate"] = $baseday; |
|
| 162 | + $changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]); |
|
| 163 | + $changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]); |
|
| 164 | + |
|
| 165 | + if (array_key_exists($this->proptags["subject"], $exception_props)) { |
|
| 166 | + $changed_item["subject"] = $exception_props[$this->proptags["subject"]]; |
|
| 167 | + } |
|
| 168 | + |
|
| 169 | + if (array_key_exists($this->proptags["location"], $exception_props)) { |
|
| 170 | + $changed_item["location"] = $exception_props[$this->proptags["location"]]; |
|
| 171 | + } |
|
| 172 | + |
|
| 173 | + if (array_key_exists($this->proptags["label"], $exception_props)) { |
|
| 174 | + $changed_item["label"] = $exception_props[$this->proptags["label"]]; |
|
| 175 | + } |
|
| 176 | + |
|
| 177 | + if (array_key_exists($this->proptags["reminder"], $exception_props)) { |
|
| 178 | + $changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]]; |
|
| 179 | + } |
|
| 180 | + |
|
| 181 | + if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) { |
|
| 182 | + $changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]]; |
|
| 183 | + } |
|
| 184 | + |
|
| 185 | + if (array_key_exists($this->proptags["alldayevent"], $exception_props)) { |
|
| 186 | + $changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]]; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + if (array_key_exists($this->proptags["busystatus"], $exception_props)) { |
|
| 190 | + $changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]]; |
|
| 191 | + } |
|
| 192 | + |
|
| 193 | + // Add the changed occurrence to the list |
|
| 194 | + array_push($this->recur["changed_occurrences"], $changed_item); |
|
| 195 | + } |
|
| 196 | + else { |
|
| 197 | + // Delete the occurrence by placing it in the deleted occurrences list |
|
| 198 | + array_push($this->recur["deleted_occurrences"], $baseday); |
|
| 199 | + } |
|
| 200 | + |
|
| 201 | + // Turn on hideattachments, because the attachments in this item are the exceptions |
|
| 202 | + mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]); |
|
| 203 | + |
|
| 204 | + // Save recurrence data to message |
|
| 205 | + $this->saveRecurrence(); |
|
| 206 | + |
|
| 207 | + return true; |
|
| 208 | + } |
|
| 209 | + |
|
| 210 | + /** |
|
| 211 | + * Modifies an existing exception, but only updates the given properties |
|
| 212 | + * NOTE: You can't remove properties from an exception, only add new ones. |
|
| 213 | + * |
|
| 214 | + * @param mixed $exception_props |
|
| 215 | + * @param mixed $base_date |
|
| 216 | + * @param mixed $exception_recips |
|
| 217 | + * @param mixed $copy_attach_from |
|
| 218 | + */ |
|
| 219 | + public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false) { |
|
| 220 | + if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) { |
|
| 221 | + return false; |
|
| 222 | + } |
|
| 223 | + |
|
| 224 | + $baseday = $this->dayStartOf($base_date); |
|
| 225 | + $extomodify = false; |
|
| 226 | + |
|
| 227 | + for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) { |
|
| 228 | + if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) { |
|
| 229 | + $extomodify = &$this->recur["changed_occurrences"][$i]; |
|
| 230 | + } |
|
| 231 | + } |
|
| 232 | + |
|
| 233 | + if (!$extomodify) { |
|
| 234 | + return false; |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + // remove basedate property as we want to preserve the old value |
|
| 238 | + // client will send basedate with time part as zero, so discard that value |
|
| 239 | + unset($exception_props[$this->proptags["basedate"]]); |
|
| 240 | + |
|
| 241 | + if (array_key_exists($this->proptags["startdate"], $exception_props)) { |
|
| 242 | + $extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); |
|
| 243 | + } |
|
| 244 | + |
|
| 245 | + if (array_key_exists($this->proptags["duedate"], $exception_props)) { |
|
| 246 | + $extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
|
| 247 | + } |
|
| 248 | + |
|
| 249 | + if (array_key_exists($this->proptags["subject"], $exception_props)) { |
|
| 250 | + $extomodify["subject"] = $exception_props[$this->proptags["subject"]]; |
|
| 251 | + } |
|
| 252 | + |
|
| 253 | + if (array_key_exists($this->proptags["location"], $exception_props)) { |
|
| 254 | + $extomodify["location"] = $exception_props[$this->proptags["location"]]; |
|
| 255 | + } |
|
| 256 | + |
|
| 257 | + if (array_key_exists($this->proptags["label"], $exception_props)) { |
|
| 258 | + $extomodify["label"] = $exception_props[$this->proptags["label"]]; |
|
| 259 | + } |
|
| 260 | + |
|
| 261 | + if (array_key_exists($this->proptags["reminder"], $exception_props)) { |
|
| 262 | + $extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]]; |
|
| 263 | + } |
|
| 264 | + |
|
| 265 | + if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) { |
|
| 266 | + $extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]]; |
|
| 267 | + } |
|
| 268 | + |
|
| 269 | + if (array_key_exists($this->proptags["alldayevent"], $exception_props)) { |
|
| 270 | + $extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]]; |
|
| 271 | + } |
|
| 272 | + |
|
| 273 | + if (array_key_exists($this->proptags["busystatus"], $exception_props)) { |
|
| 274 | + $extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]]; |
|
| 275 | + } |
|
| 276 | + |
|
| 277 | + $exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"; |
|
| 278 | + |
|
| 279 | + // synchronize commonstart/commonend with startdate/duedate |
|
| 280 | + if (isset($exception_props[$this->proptags["startdate"]])) { |
|
| 281 | + $exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]]; |
|
| 282 | + } |
|
| 283 | + |
|
| 284 | + if (isset($exception_props[$this->proptags["duedate"]])) { |
|
| 285 | + $exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]]; |
|
| 286 | + } |
|
| 287 | + |
|
| 288 | + $attach = $this->getExceptionAttachment($baseday); |
|
| 289 | + if (!$attach) { |
|
| 290 | + if ($copy_attach_from) { |
|
| 291 | + $this->deleteExceptionAttachment($base_date); |
|
| 292 | + $this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from); |
|
| 293 | + } |
|
| 294 | + else { |
|
| 295 | + $this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from); |
|
| 296 | + } |
|
| 297 | + } |
|
| 298 | + else { |
|
| 299 | + $message = mapi_attach_openobj($attach, MAPI_MODIFY); |
|
| 300 | + |
|
| 301 | + // Set exception properties on embedded message and save |
|
| 302 | + mapi_setprops($message, $exception_props); |
|
| 303 | + $this->setExceptionRecipients($message, $exception_recips, false); |
|
| 304 | + mapi_savechanges($message); |
|
| 305 | + |
|
| 306 | + // If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME' |
|
| 307 | + // on the attachment which holds the embedded msg and save everything. |
|
| 308 | + $props = []; |
|
| 309 | + if (isset($exception_props[$this->proptags["startdate"]])) { |
|
| 310 | + $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); |
|
| 311 | + } |
|
| 312 | + if (isset($exception_props[$this->proptags["duedate"]])) { |
|
| 313 | + $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
|
| 314 | + } |
|
| 315 | + if (!empty($props)) { |
|
| 316 | + mapi_setprops($attach, $props); |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + mapi_savechanges($attach); |
|
| 320 | + } |
|
| 321 | + |
|
| 322 | + // Save recurrence data to message |
|
| 323 | + $this->saveRecurrence(); |
|
| 324 | + |
|
| 325 | + return true; |
|
| 326 | + } |
|
| 327 | + |
|
| 328 | + // Checks to see if the following is true: |
|
| 329 | + // 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration) |
|
| 330 | + // 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!) |
|
| 331 | + // |
|
| 332 | + // Both $basedate and $start are in LOCAL time |
|
| 333 | + public function isValidExceptionDate($basedate, $start) { |
|
| 334 | + // The way we do this is to look at the days that we're 'moving' the item in the exception. Each |
|
| 335 | + // of these days may only contain the item that we're modifying. Any other item violates the rules. |
|
| 336 | + |
|
| 337 | + if ($this->isException($basedate)) { |
|
| 338 | + // If we're modifying an exception, we want to look at the days that we're 'moving' compared to where |
|
| 339 | + // the exception used to be. |
|
| 340 | + $oldexception = $this->getChangeException($basedate); |
|
| 341 | + $prevday = $this->dayStartOf($oldexception["start"]); |
|
| 342 | + } |
|
| 343 | + else { |
|
| 344 | + // If its a new exception, we want to look at the original placement of this item. |
|
| 345 | + $prevday = $basedate; |
|
| 346 | + } |
|
| 347 | + |
|
| 348 | + $startday = $this->dayStartOf($start); |
|
| 349 | + |
|
| 350 | + // Get all the occurrences on the days between the basedate (may be reversed) |
|
| 351 | + if ($prevday < $startday) { |
|
| 352 | + $items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60)); |
|
| 353 | + } |
|
| 354 | + else { |
|
| 355 | + $items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60)); |
|
| 356 | + } |
|
| 357 | + |
|
| 358 | + // There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range, |
|
| 359 | + // then we abort the change, since one of the rules has been violated. |
|
| 360 | + return count($items) == 1; |
|
| 361 | + } |
|
| 362 | + |
|
| 363 | + /** |
|
| 364 | + * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:. |
|
| 365 | + * |
|
| 366 | + * Both must be true: |
|
| 367 | + * - reminder time of this item is not before the starttime of the previous recurring item |
|
| 368 | + * - reminder time of the next item is not before the starttime of this item |
|
| 369 | + * |
|
| 370 | + * @param date $basedate the base date of the exception (LOCAL time of non-exception occurrence) |
|
| 371 | + * @param string $reminderminutes reminder minutes which is set of the item |
|
| 372 | + * @param date $startdate the startdate of the selected item |
|
| 373 | + * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE) |
|
| 374 | + */ |
|
| 375 | + public function isValidReminderTime($basedate, $reminderminutes, $startdate) { |
|
| 376 | + // get all occurrence items before the seleceted items occurrence starttime |
|
| 377 | + $occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate)); |
|
| 378 | + |
|
| 379 | + if (!empty($occitems)) { |
|
| 380 | + // as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems . |
|
| 381 | + $previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]]; |
|
| 382 | + |
|
| 383 | + // if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed |
|
| 384 | + if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) { |
|
| 385 | + return false; |
|
| 386 | + } |
|
| 387 | + } |
|
| 388 | + |
|
| 389 | + // Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence) |
|
| 390 | + $currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true); |
|
| 391 | + |
|
| 392 | + // If there are another two occurrences, then the first is the current occurrence, and the one after that |
|
| 393 | + // is the next occurrence. |
|
| 394 | + if (count($currentOcc) > 1) { |
|
| 395 | + $next = $currentOcc[1]; |
|
| 396 | + // Get reminder time of the next occurrence. |
|
| 397 | + $nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60); |
|
| 398 | + // If the reminder time of the next item is before the start of this item, then that's not allowed |
|
| 399 | + if ($nextOccReminderTime <= $startdate) { |
|
| 400 | + return false; |
|
| 401 | + } |
|
| 402 | + } |
|
| 403 | + |
|
| 404 | + // All was ok |
|
| 405 | + return true; |
|
| 406 | + } |
|
| 407 | + |
|
| 408 | + public function setRecurrence($tz, $recur) { |
|
| 409 | + // only reset timezone if specified |
|
| 410 | + if ($tz) { |
|
| 411 | + $this->tz = $tz; |
|
| 412 | + } |
|
| 413 | + |
|
| 414 | + $this->recur = $recur; |
|
| 415 | + |
|
| 416 | + if (!isset($this->recur["changed_occurrences"])) { |
|
| 417 | + $this->recur["changed_occurrences"] = []; |
|
| 418 | + } |
|
| 419 | + |
|
| 420 | + if (!isset($this->recur["deleted_occurrences"])) { |
|
| 421 | + $this->recur["deleted_occurrences"] = []; |
|
| 422 | + } |
|
| 423 | + |
|
| 424 | + $this->deleteAttachments(); |
|
| 425 | + $this->saveRecurrence(); |
|
| 426 | + |
|
| 427 | + // if client has not set the recurring_pattern then we should generate it and save it |
|
| 428 | + $messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]); |
|
| 429 | + if (empty($messageProps[$this->proptags["recurring_pattern"]])) { |
|
| 430 | + $this->saveRecurrencePattern(); |
|
| 431 | + } |
|
| 432 | + } |
|
| 433 | + |
|
| 434 | + // Returns the start or end time of the occurrence on the given base date. |
|
| 435 | + // This assumes that the basedate you supply is in LOCAL time |
|
| 436 | + public function getOccurrenceStart($basedate) { |
|
| 437 | + $daystart = $this->dayStartOf($basedate); |
|
| 438 | + |
|
| 439 | + return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60); |
|
| 440 | + } |
|
| 441 | + |
|
| 442 | + public function getOccurrenceEnd($basedate) { |
|
| 443 | + $daystart = $this->dayStartOf($basedate); |
|
| 444 | + |
|
| 445 | + return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60); |
|
| 446 | + } |
|
| 447 | + |
|
| 448 | + // Backwards compatible code |
|
| 449 | + public function getOccurrenceStart($basedate) { |
|
| 450 | + return $this->getOccurrenceStart($basedate); |
|
| 451 | + } |
|
| 452 | + |
|
| 453 | + public function getOccurrenceEnd($basedate) { |
|
| 454 | + return $this->getOccurrenceEnd($basedate); |
|
| 455 | + } |
|
| 456 | + |
|
| 457 | + /** |
|
| 458 | + * This function returns the next remindertime starting from $timestamp |
|
| 459 | + * When no next reminder exists, false is returned. |
|
| 460 | + * |
|
| 461 | + * Note: Before saving this new reminder time (when snoozing), you must check for |
|
| 462 | + * yourself if this reminder time is earlier than your snooze time, else |
|
| 463 | + * use your snooze time and not this reminder time. |
|
| 464 | + * |
|
| 465 | + * @param mixed $timestamp |
|
| 466 | + */ |
|
| 467 | + public function getNextReminderTime($timestamp) { |
|
| 468 | + /** |
|
| 469 | + * Get next item from now until forever, but max 1 item with reminder set |
|
| 470 | + * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT.... |
|
| 471 | + * Here for getting next 10 occurrences assuming that next here we will be able to find |
|
| 472 | + * nextreminder occurrence in 10 occureneces. |
|
| 473 | + */ |
|
| 474 | + $items = $this->getItems($timestamp, 0x7FF00000, 10, true); |
|
| 475 | + |
|
| 476 | + // Initially setting nextreminder to false so when no next reminder exists, false is returned. |
|
| 477 | + $nextreminder = false; |
|
| 478 | + /* |
|
| 479 | 479 | * Loop through all reminder which we get in items variable |
| 480 | 480 | * and check whether the remindertime is greater than timestamp. |
| 481 | 481 | * On the first occurrence of greater nextreminder break the loop |
| 482 | 482 | * and return the value to calling function. |
| 483 | 483 | */ |
| 484 | - for ($i = 0, $len = count($items); $i < $len; ++$i) { |
|
| 485 | - $item = $items[$i]; |
|
| 486 | - $tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60); |
|
| 487 | - |
|
| 488 | - // If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop. |
|
| 489 | - if ($tempnextreminder > $timestamp) { |
|
| 490 | - $nextreminder = $tempnextreminder; |
|
| 491 | - |
|
| 492 | - break; |
|
| 493 | - } |
|
| 494 | - } |
|
| 495 | - |
|
| 496 | - return $nextreminder; |
|
| 497 | - } |
|
| 498 | - |
|
| 499 | - /** |
|
| 500 | - * Note: Static function, more like a utility function. |
|
| 501 | - * |
|
| 502 | - * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are |
|
| 503 | - * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval |
|
| 504 | - * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item |
|
| 505 | - * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>. |
|
| 506 | - * |
|
| 507 | - * @param $store resource The store in which the calendar resides |
|
| 508 | - * @param $calendar resource The calendar to get the items from |
|
| 509 | - * @param $viewstart int Timestamp of beginning of view window |
|
| 510 | - * @param $viewend int Timestamp of end of view window |
|
| 511 | - * @param $propsrequested array Array of properties to return |
|
| 512 | - * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is |
|
| 513 | - * expanded so that it seems that there are only many single appointments in the table. |
|
| 514 | - */ |
|
| 515 | - public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) { |
|
| 516 | - return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested); |
|
| 517 | - } |
|
| 518 | - |
|
| 519 | - /* |
|
| 484 | + for ($i = 0, $len = count($items); $i < $len; ++$i) { |
|
| 485 | + $item = $items[$i]; |
|
| 486 | + $tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60); |
|
| 487 | + |
|
| 488 | + // If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop. |
|
| 489 | + if ($tempnextreminder > $timestamp) { |
|
| 490 | + $nextreminder = $tempnextreminder; |
|
| 491 | + |
|
| 492 | + break; |
|
| 493 | + } |
|
| 494 | + } |
|
| 495 | + |
|
| 496 | + return $nextreminder; |
|
| 497 | + } |
|
| 498 | + |
|
| 499 | + /** |
|
| 500 | + * Note: Static function, more like a utility function. |
|
| 501 | + * |
|
| 502 | + * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are |
|
| 503 | + * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval |
|
| 504 | + * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item |
|
| 505 | + * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>. |
|
| 506 | + * |
|
| 507 | + * @param $store resource The store in which the calendar resides |
|
| 508 | + * @param $calendar resource The calendar to get the items from |
|
| 509 | + * @param $viewstart int Timestamp of beginning of view window |
|
| 510 | + * @param $viewend int Timestamp of end of view window |
|
| 511 | + * @param $propsrequested array Array of properties to return |
|
| 512 | + * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is |
|
| 513 | + * expanded so that it seems that there are only many single appointments in the table. |
|
| 514 | + */ |
|
| 515 | + public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) { |
|
| 516 | + return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested); |
|
| 517 | + } |
|
| 518 | + |
|
| 519 | + /* |
|
| 520 | 520 | * CODE BELOW THIS LINE IS FOR INTERNAL USE ONLY |
| 521 | 521 | ***************************************************************************************************************** |
| 522 | 522 | */ |
| 523 | 523 | |
| 524 | - /** |
|
| 525 | - * Generates and stores recurrence pattern string to recurring_pattern property. |
|
| 526 | - */ |
|
| 527 | - public function saveRecurrencePattern() { |
|
| 528 | - // Start formatting the properties in such a way we can apply |
|
| 529 | - // them directly into the recurrence pattern. |
|
| 530 | - $type = $this->recur['type']; |
|
| 531 | - $everyn = $this->recur['everyn']; |
|
| 532 | - $start = $this->recur['start']; |
|
| 533 | - $end = $this->recur['end']; |
|
| 534 | - $term = $this->recur['term']; |
|
| 535 | - $numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false; |
|
| 536 | - $startocc = $this->recur['startocc']; |
|
| 537 | - $endocc = $this->recur['endocc']; |
|
| 538 | - $pattern = ''; |
|
| 539 | - $occSingleDayRank = false; |
|
| 540 | - $occTimeRange = ($startocc != 0 && $endocc != 0); |
|
| 541 | - |
|
| 542 | - switch ($type) { |
|
| 543 | - // Daily |
|
| 544 | - case 0x0A: |
|
| 545 | - if ($everyn == 1) { |
|
| 546 | - $type = _('workday'); |
|
| 547 | - $occSingleDayRank = true; |
|
| 548 | - } |
|
| 549 | - elseif ($everyn == (24 * 60)) { |
|
| 550 | - $type = _('day'); |
|
| 551 | - $occSingleDayRank = true; |
|
| 552 | - } |
|
| 553 | - else { |
|
| 554 | - $everyn /= (24 * 60); |
|
| 555 | - $type = _('days'); |
|
| 556 | - $occSingleDayRank = false; |
|
| 557 | - } |
|
| 558 | - |
|
| 559 | - break; |
|
| 560 | - // Weekly |
|
| 561 | - case 0x0B: |
|
| 562 | - if ($everyn == 1) { |
|
| 563 | - $type = _('week'); |
|
| 564 | - $occSingleDayRank = true; |
|
| 565 | - } |
|
| 566 | - else { |
|
| 567 | - $type = _('weeks'); |
|
| 568 | - $occSingleDayRank = false; |
|
| 569 | - } |
|
| 570 | - |
|
| 571 | - break; |
|
| 572 | - // Monthly |
|
| 573 | - case 0x0C: |
|
| 574 | - if ($everyn == 1) { |
|
| 575 | - $type = _('month'); |
|
| 576 | - $occSingleDayRank = true; |
|
| 577 | - } |
|
| 578 | - else { |
|
| 579 | - $type = _('months'); |
|
| 580 | - $occSingleDayRank = false; |
|
| 581 | - } |
|
| 582 | - |
|
| 583 | - break; |
|
| 584 | - // Yearly |
|
| 585 | - case 0x0D: |
|
| 586 | - if ($everyn <= 12) { |
|
| 587 | - $everyn = 1; |
|
| 588 | - $type = _('year'); |
|
| 589 | - $occSingleDayRank = true; |
|
| 590 | - } |
|
| 591 | - else { |
|
| 592 | - $everyn = $everyn / 12; |
|
| 593 | - $type = _('years'); |
|
| 594 | - $occSingleDayRank = false; |
|
| 595 | - } |
|
| 596 | - |
|
| 597 | - break; |
|
| 598 | - } |
|
| 599 | - |
|
| 600 | - // get timings of the first occurrence |
|
| 601 | - $firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start; |
|
| 602 | - $firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end; |
|
| 603 | - |
|
| 604 | - $start = gmdate(_('d-m-Y'), $firstoccstartdate); |
|
| 605 | - $end = gmdate(_('d-m-Y'), $firstoccenddate); |
|
| 606 | - $startocc = gmdate(_('G:i'), $firstoccstartdate); |
|
| 607 | - $endocc = gmdate(_('G:i'), $firstoccenddate); |
|
| 608 | - |
|
| 609 | - // Based on the properties, we need to generate the recurrence pattern string. |
|
| 610 | - // This is obviously very easy since we can simply concatenate a bunch of strings, |
|
| 611 | - // however this messes up translations for languages which order their words |
|
| 612 | - // differently. |
|
| 613 | - // To improve translation quality we create a series of default strings, in which |
|
| 614 | - // we only have to fill in the correct variables. The base string is thus selected |
|
| 615 | - // based on the available properties. |
|
| 616 | - if ($term == 0x23) { |
|
| 617 | - // Never ends |
|
| 618 | - if ($occTimeRange) { |
|
| 619 | - if ($occSingleDayRank) { |
|
| 620 | - $pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc); |
|
| 621 | - } |
|
| 622 | - else { |
|
| 623 | - $pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc); |
|
| 624 | - } |
|
| 625 | - } |
|
| 626 | - else { |
|
| 627 | - if ($occSingleDayRank) { |
|
| 628 | - $pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start); |
|
| 629 | - } |
|
| 630 | - else { |
|
| 631 | - $pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start); |
|
| 632 | - } |
|
| 633 | - } |
|
| 634 | - } |
|
| 635 | - elseif ($term == 0x22) { |
|
| 636 | - // After a number of times |
|
| 637 | - if ($occTimeRange) { |
|
| 638 | - if ($occSingleDayRank) { |
|
| 639 | - $pattern = sprintf(ngettext( |
|
| 640 | - 'Occurs every %s effective %s for %s occurrence from %s to %s.', |
|
| 641 | - 'Occurs every %s effective %s for %s occurrences from %s to %s.', |
|
| 642 | - $numocc |
|
| 643 | - ), $type, $start, $numocc, $startocc, $endocc); |
|
| 644 | - } |
|
| 645 | - else { |
|
| 646 | - $pattern = sprintf(ngettext( |
|
| 647 | - 'Occurs every %s %s effective %s for %s occurrence from %s to %s.', |
|
| 648 | - 'Occurs every %s %s effective %s for %s occurrences %s to %s.', |
|
| 649 | - $numocc |
|
| 650 | - ), $everyn, $type, $start, $numocc, $startocc, $endocc); |
|
| 651 | - } |
|
| 652 | - } |
|
| 653 | - else { |
|
| 654 | - if ($occSingleDayRank) { |
|
| 655 | - $pattern = sprintf(ngettext( |
|
| 656 | - 'Occurs every %s effective %s for %s occurrence.', |
|
| 657 | - 'Occurs every %s effective %s for %s occurrences.', |
|
| 658 | - $numocc |
|
| 659 | - ), $type, $start, $numocc); |
|
| 660 | - } |
|
| 661 | - else { |
|
| 662 | - $pattern = sprintf(ngettext( |
|
| 663 | - 'Occurs every %s %s effective %s for %s occurrence.', |
|
| 664 | - 'Occurs every %s %s effective %s for %s occurrences.', |
|
| 665 | - $numocc |
|
| 666 | - ), $everyn, $type, $start, $numocc); |
|
| 667 | - } |
|
| 668 | - } |
|
| 669 | - } |
|
| 670 | - elseif ($term == 0x21) { |
|
| 671 | - // After the given enddate |
|
| 672 | - if ($occTimeRange) { |
|
| 673 | - if ($occSingleDayRank) { |
|
| 674 | - $pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc); |
|
| 675 | - } |
|
| 676 | - else { |
|
| 677 | - $pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc); |
|
| 678 | - } |
|
| 679 | - } |
|
| 680 | - else { |
|
| 681 | - if ($occSingleDayRank) { |
|
| 682 | - $pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end); |
|
| 683 | - } |
|
| 684 | - else { |
|
| 685 | - $pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end); |
|
| 686 | - } |
|
| 687 | - } |
|
| 688 | - } |
|
| 689 | - |
|
| 690 | - if (!empty($pattern)) { |
|
| 691 | - mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]); |
|
| 692 | - } |
|
| 693 | - } |
|
| 694 | - |
|
| 695 | - /* |
|
| 524 | + /** |
|
| 525 | + * Generates and stores recurrence pattern string to recurring_pattern property. |
|
| 526 | + */ |
|
| 527 | + public function saveRecurrencePattern() { |
|
| 528 | + // Start formatting the properties in such a way we can apply |
|
| 529 | + // them directly into the recurrence pattern. |
|
| 530 | + $type = $this->recur['type']; |
|
| 531 | + $everyn = $this->recur['everyn']; |
|
| 532 | + $start = $this->recur['start']; |
|
| 533 | + $end = $this->recur['end']; |
|
| 534 | + $term = $this->recur['term']; |
|
| 535 | + $numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false; |
|
| 536 | + $startocc = $this->recur['startocc']; |
|
| 537 | + $endocc = $this->recur['endocc']; |
|
| 538 | + $pattern = ''; |
|
| 539 | + $occSingleDayRank = false; |
|
| 540 | + $occTimeRange = ($startocc != 0 && $endocc != 0); |
|
| 541 | + |
|
| 542 | + switch ($type) { |
|
| 543 | + // Daily |
|
| 544 | + case 0x0A: |
|
| 545 | + if ($everyn == 1) { |
|
| 546 | + $type = _('workday'); |
|
| 547 | + $occSingleDayRank = true; |
|
| 548 | + } |
|
| 549 | + elseif ($everyn == (24 * 60)) { |
|
| 550 | + $type = _('day'); |
|
| 551 | + $occSingleDayRank = true; |
|
| 552 | + } |
|
| 553 | + else { |
|
| 554 | + $everyn /= (24 * 60); |
|
| 555 | + $type = _('days'); |
|
| 556 | + $occSingleDayRank = false; |
|
| 557 | + } |
|
| 558 | + |
|
| 559 | + break; |
|
| 560 | + // Weekly |
|
| 561 | + case 0x0B: |
|
| 562 | + if ($everyn == 1) { |
|
| 563 | + $type = _('week'); |
|
| 564 | + $occSingleDayRank = true; |
|
| 565 | + } |
|
| 566 | + else { |
|
| 567 | + $type = _('weeks'); |
|
| 568 | + $occSingleDayRank = false; |
|
| 569 | + } |
|
| 570 | + |
|
| 571 | + break; |
|
| 572 | + // Monthly |
|
| 573 | + case 0x0C: |
|
| 574 | + if ($everyn == 1) { |
|
| 575 | + $type = _('month'); |
|
| 576 | + $occSingleDayRank = true; |
|
| 577 | + } |
|
| 578 | + else { |
|
| 579 | + $type = _('months'); |
|
| 580 | + $occSingleDayRank = false; |
|
| 581 | + } |
|
| 582 | + |
|
| 583 | + break; |
|
| 584 | + // Yearly |
|
| 585 | + case 0x0D: |
|
| 586 | + if ($everyn <= 12) { |
|
| 587 | + $everyn = 1; |
|
| 588 | + $type = _('year'); |
|
| 589 | + $occSingleDayRank = true; |
|
| 590 | + } |
|
| 591 | + else { |
|
| 592 | + $everyn = $everyn / 12; |
|
| 593 | + $type = _('years'); |
|
| 594 | + $occSingleDayRank = false; |
|
| 595 | + } |
|
| 596 | + |
|
| 597 | + break; |
|
| 598 | + } |
|
| 599 | + |
|
| 600 | + // get timings of the first occurrence |
|
| 601 | + $firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start; |
|
| 602 | + $firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end; |
|
| 603 | + |
|
| 604 | + $start = gmdate(_('d-m-Y'), $firstoccstartdate); |
|
| 605 | + $end = gmdate(_('d-m-Y'), $firstoccenddate); |
|
| 606 | + $startocc = gmdate(_('G:i'), $firstoccstartdate); |
|
| 607 | + $endocc = gmdate(_('G:i'), $firstoccenddate); |
|
| 608 | + |
|
| 609 | + // Based on the properties, we need to generate the recurrence pattern string. |
|
| 610 | + // This is obviously very easy since we can simply concatenate a bunch of strings, |
|
| 611 | + // however this messes up translations for languages which order their words |
|
| 612 | + // differently. |
|
| 613 | + // To improve translation quality we create a series of default strings, in which |
|
| 614 | + // we only have to fill in the correct variables. The base string is thus selected |
|
| 615 | + // based on the available properties. |
|
| 616 | + if ($term == 0x23) { |
|
| 617 | + // Never ends |
|
| 618 | + if ($occTimeRange) { |
|
| 619 | + if ($occSingleDayRank) { |
|
| 620 | + $pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc); |
|
| 621 | + } |
|
| 622 | + else { |
|
| 623 | + $pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc); |
|
| 624 | + } |
|
| 625 | + } |
|
| 626 | + else { |
|
| 627 | + if ($occSingleDayRank) { |
|
| 628 | + $pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start); |
|
| 629 | + } |
|
| 630 | + else { |
|
| 631 | + $pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start); |
|
| 632 | + } |
|
| 633 | + } |
|
| 634 | + } |
|
| 635 | + elseif ($term == 0x22) { |
|
| 636 | + // After a number of times |
|
| 637 | + if ($occTimeRange) { |
|
| 638 | + if ($occSingleDayRank) { |
|
| 639 | + $pattern = sprintf(ngettext( |
|
| 640 | + 'Occurs every %s effective %s for %s occurrence from %s to %s.', |
|
| 641 | + 'Occurs every %s effective %s for %s occurrences from %s to %s.', |
|
| 642 | + $numocc |
|
| 643 | + ), $type, $start, $numocc, $startocc, $endocc); |
|
| 644 | + } |
|
| 645 | + else { |
|
| 646 | + $pattern = sprintf(ngettext( |
|
| 647 | + 'Occurs every %s %s effective %s for %s occurrence from %s to %s.', |
|
| 648 | + 'Occurs every %s %s effective %s for %s occurrences %s to %s.', |
|
| 649 | + $numocc |
|
| 650 | + ), $everyn, $type, $start, $numocc, $startocc, $endocc); |
|
| 651 | + } |
|
| 652 | + } |
|
| 653 | + else { |
|
| 654 | + if ($occSingleDayRank) { |
|
| 655 | + $pattern = sprintf(ngettext( |
|
| 656 | + 'Occurs every %s effective %s for %s occurrence.', |
|
| 657 | + 'Occurs every %s effective %s for %s occurrences.', |
|
| 658 | + $numocc |
|
| 659 | + ), $type, $start, $numocc); |
|
| 660 | + } |
|
| 661 | + else { |
|
| 662 | + $pattern = sprintf(ngettext( |
|
| 663 | + 'Occurs every %s %s effective %s for %s occurrence.', |
|
| 664 | + 'Occurs every %s %s effective %s for %s occurrences.', |
|
| 665 | + $numocc |
|
| 666 | + ), $everyn, $type, $start, $numocc); |
|
| 667 | + } |
|
| 668 | + } |
|
| 669 | + } |
|
| 670 | + elseif ($term == 0x21) { |
|
| 671 | + // After the given enddate |
|
| 672 | + if ($occTimeRange) { |
|
| 673 | + if ($occSingleDayRank) { |
|
| 674 | + $pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc); |
|
| 675 | + } |
|
| 676 | + else { |
|
| 677 | + $pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc); |
|
| 678 | + } |
|
| 679 | + } |
|
| 680 | + else { |
|
| 681 | + if ($occSingleDayRank) { |
|
| 682 | + $pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end); |
|
| 683 | + } |
|
| 684 | + else { |
|
| 685 | + $pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end); |
|
| 686 | + } |
|
| 687 | + } |
|
| 688 | + } |
|
| 689 | + |
|
| 690 | + if (!empty($pattern)) { |
|
| 691 | + mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]); |
|
| 692 | + } |
|
| 693 | + } |
|
| 694 | + |
|
| 695 | + /* |
|
| 696 | 696 | * Remove an exception by base_date. This is the base date in local daystart time |
| 697 | 697 | */ |
| 698 | - public function deleteException($base_date) { |
|
| 699 | - // Remove all exceptions on $base_date from the deleted and changed occurrences lists |
|
| 700 | - |
|
| 701 | - // Remove all items in $todelete from deleted_occurrences |
|
| 702 | - $new = []; |
|
| 703 | - |
|
| 704 | - foreach ($this->recur["deleted_occurrences"] as $entry) { |
|
| 705 | - if ($entry != $base_date) { |
|
| 706 | - $new[] = $entry; |
|
| 707 | - } |
|
| 708 | - } |
|
| 709 | - $this->recur["deleted_occurrences"] = $new; |
|
| 710 | - |
|
| 711 | - $new = []; |
|
| 712 | - |
|
| 713 | - foreach ($this->recur["changed_occurrences"] as $entry) { |
|
| 714 | - if (!$this->isSameDay($entry["basedate"], $base_date)) { |
|
| 715 | - $new[] = $entry; |
|
| 716 | - } |
|
| 717 | - else { |
|
| 718 | - $this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60)); |
|
| 719 | - } |
|
| 720 | - } |
|
| 721 | - |
|
| 722 | - $this->recur["changed_occurrences"] = $new; |
|
| 723 | - } |
|
| 724 | - |
|
| 725 | - /** |
|
| 726 | - * Function which saves the exception data in an attachment. |
|
| 727 | - * |
|
| 728 | - * @param array $exception_props the exception data (like any other MAPI appointment) |
|
| 729 | - * @param array $exception_recips list of recipients |
|
| 730 | - * @param mapi_message $copy_attach_from mapi message from which attachments should be copied |
|
| 731 | - */ |
|
| 732 | - public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) { |
|
| 733 | - // Create new attachment. |
|
| 734 | - $attachment = mapi_message_createattach($this->message); |
|
| 735 | - $props = []; |
|
| 736 | - $props[PR_ATTACHMENT_FLAGS] = 2; |
|
| 737 | - $props[PR_ATTACHMENT_HIDDEN] = true; |
|
| 738 | - $props[PR_ATTACHMENT_LINKID] = 0; |
|
| 739 | - $props[PR_ATTACH_FLAGS] = 0; |
|
| 740 | - $props[PR_ATTACH_METHOD] = 5; |
|
| 741 | - $props[PR_DISPLAY_NAME] = "Exception"; |
|
| 742 | - $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); |
|
| 743 | - $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
|
| 744 | - mapi_setprops($attachment, $props); |
|
| 745 | - |
|
| 746 | - $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY); |
|
| 747 | - |
|
| 748 | - if ($copy_attach_from) { |
|
| 749 | - $attachmentTable = mapi_message_getattachmenttable($copy_attach_from); |
|
| 750 | - if ($attachmentTable) { |
|
| 751 | - $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
|
| 752 | - |
|
| 753 | - foreach ($attachments as $attach_props) { |
|
| 754 | - $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]); |
|
| 755 | - $attach_newResourceMsg = mapi_message_createattach($imessage); |
|
| 756 | - mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
|
| 757 | - mapi_savechanges($attach_newResourceMsg); |
|
| 758 | - } |
|
| 759 | - } |
|
| 760 | - } |
|
| 761 | - |
|
| 762 | - $props = $props + $exception_props; |
|
| 763 | - |
|
| 764 | - // FIXME: the following piece of code is written to fix the creation |
|
| 765 | - // of an exception. This is only a quickfix as it is not yet possible |
|
| 766 | - // to change an existing exception. |
|
| 767 | - // remove mv properties when needed |
|
| 768 | - foreach ($props as $propTag => $propVal) { |
|
| 769 | - if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) { |
|
| 770 | - unset($props[$propTag]); |
|
| 771 | - } |
|
| 772 | - } |
|
| 773 | - |
|
| 774 | - mapi_setprops($imessage, $props); |
|
| 775 | - |
|
| 776 | - $this->setExceptionRecipients($imessage, $exception_recips, true); |
|
| 777 | - |
|
| 778 | - mapi_savechanges($imessage); |
|
| 779 | - mapi_savechanges($attachment); |
|
| 780 | - } |
|
| 781 | - |
|
| 782 | - /** |
|
| 783 | - * Function which deletes the attachment of an exception. |
|
| 784 | - * |
|
| 785 | - * @param date $base_date base date of the attachment. Should be in GMT. The attachment |
|
| 786 | - * actually saves the real time of the original date, so we have |
|
| 787 | - * to check whether it's on the same day. |
|
| 788 | - */ |
|
| 789 | - public function deleteExceptionAttachment($base_date) { |
|
| 790 | - $attachments = mapi_message_getattachmenttable($this->message); |
|
| 791 | - $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM]); |
|
| 792 | - |
|
| 793 | - foreach ($attachTable as $attachRow) { |
|
| 794 | - $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 795 | - $exception = mapi_attach_openobj($tempattach); |
|
| 796 | - |
|
| 797 | - $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]); |
|
| 798 | - |
|
| 799 | - if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) { |
|
| 800 | - mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 801 | - } |
|
| 802 | - } |
|
| 803 | - } |
|
| 804 | - |
|
| 805 | - /** |
|
| 806 | - * Function which deletes all attachments of a message. |
|
| 807 | - */ |
|
| 808 | - public function deleteAttachments() { |
|
| 809 | - $attachments = mapi_message_getattachmenttable($this->message); |
|
| 810 | - $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]); |
|
| 811 | - |
|
| 812 | - foreach ($attachTable as $attachRow) { |
|
| 813 | - if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) { |
|
| 814 | - mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 815 | - } |
|
| 816 | - } |
|
| 817 | - } |
|
| 818 | - |
|
| 819 | - /** |
|
| 820 | - * Get an exception attachment based on its basedate. |
|
| 821 | - * |
|
| 822 | - * @param mixed $base_date |
|
| 823 | - */ |
|
| 824 | - public function getExceptionAttachment($base_date) { |
|
| 825 | - // Retrieve only exceptions which are stored as embedded messages |
|
| 826 | - $attach_res = [ |
|
| 827 | - RES_AND, |
|
| 828 | - [ |
|
| 829 | - [ |
|
| 830 | - RES_PROPERTY, |
|
| 831 | - [ |
|
| 832 | - RELOP => RELOP_EQ, |
|
| 833 | - ULPROPTAG => PR_ATTACH_METHOD, |
|
| 834 | - VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG], |
|
| 835 | - ], |
|
| 836 | - ], |
|
| 837 | - ], |
|
| 838 | - ]; |
|
| 839 | - $attachments = mapi_message_getattachmenttable($this->message); |
|
| 840 | - $attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res); |
|
| 841 | - |
|
| 842 | - if (is_array($attachRows)) { |
|
| 843 | - foreach ($attachRows as $attachRow) { |
|
| 844 | - $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 845 | - $exception = mapi_attach_openobj($tempattach); |
|
| 846 | - |
|
| 847 | - $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]); |
|
| 848 | - |
|
| 849 | - if (!isset($data[$this->proptags["basedate"]])) { |
|
| 850 | - // if no basedate found then it could be embedded message so ignore it |
|
| 851 | - // we need proper restriction to exclude embedded messages as well |
|
| 852 | - continue; |
|
| 853 | - } |
|
| 854 | - |
|
| 855 | - if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) { |
|
| 856 | - return $tempattach; |
|
| 857 | - } |
|
| 858 | - } |
|
| 859 | - } |
|
| 860 | - |
|
| 861 | - return false; |
|
| 862 | - } |
|
| 863 | - |
|
| 864 | - /** |
|
| 865 | - * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met: |
|
| 866 | - * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end> |
|
| 867 | - * - The occurrence isn't specified as a deleted occurrence. |
|
| 868 | - * |
|
| 869 | - * @param array $items reference to the array to be added to |
|
| 870 | - * @param date $start start of timeframe in GMT TIME |
|
| 871 | - * @param date $end end of timeframe in GMT TIME |
|
| 872 | - * @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE |
|
| 873 | - * @param int $startocc start of occurrence since beginning of day in minutes |
|
| 874 | - * @param int $endocc end of occurrence since beginning of day in minutes |
|
| 875 | - * @param int $tz the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc ) |
|
| 876 | - * @param bool $reminderonly If TRUE, only add the item if the reminder is set |
|
| 877 | - */ |
|
| 878 | - public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) { |
|
| 879 | - $exception = $this->isException($basedate); |
|
| 880 | - if ($exception) { |
|
| 881 | - return false; |
|
| 882 | - } |
|
| 883 | - $occstart = $basedate + $startocc * 60; |
|
| 884 | - $occend = $basedate + $endocc * 60; |
|
| 885 | - |
|
| 886 | - // Convert to GMT |
|
| 887 | - $occstart = $this->toGMT($tz, $occstart); |
|
| 888 | - $occend = $this->toGMT($tz, $occend); |
|
| 889 | - |
|
| 890 | - /** |
|
| 891 | - * FIRST PART : Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot |
|
| 892 | - * see any part of the appointment. Partial overlaps DO match. |
|
| 893 | - * |
|
| 894 | - * SECOND PART : check if occurrence is not a zero duration occurrence which |
|
| 895 | - * starts at 00:00 and ends on 00:00. if it is so, then process |
|
| 896 | - * the occurrence and send it in response. |
|
| 897 | - */ |
|
| 898 | - if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) { |
|
| 899 | - return; |
|
| 900 | - } |
|
| 901 | - |
|
| 902 | - // Properties for this occurrence are the same as the main object, |
|
| 903 | - // With these properties overridden |
|
| 904 | - $newitem = $this->messageprops; |
|
| 905 | - $newitem[$this->proptags["startdate"]] = $occstart; |
|
| 906 | - $newitem[$this->proptags["duedate"]] = $occend; |
|
| 907 | - $newitem[$this->proptags["commonstart"]] = $occstart; |
|
| 908 | - $newitem[$this->proptags["commonend"]] = $occend; |
|
| 909 | - $newitem["basedate"] = $basedate; |
|
| 910 | - |
|
| 911 | - // If reminderonly is set, only add reminders |
|
| 912 | - if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) { |
|
| 913 | - return; |
|
| 914 | - } |
|
| 915 | - |
|
| 916 | - $items[] = $newitem; |
|
| 917 | - } |
|
| 918 | - |
|
| 919 | - /** |
|
| 920 | - * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe. |
|
| 921 | - * |
|
| 922 | - * @param array $items reference to the array to be added to |
|
| 923 | - * @param date $start start of timeframe in GMT TIME |
|
| 924 | - * @param date $end end of timeframe in GMT TIME |
|
| 925 | - */ |
|
| 926 | - public function processExceptionItems(&$items, $start, $end) { |
|
| 927 | - $limit = 0; |
|
| 928 | - foreach ($this->recur["changed_occurrences"] as $exception) { |
|
| 929 | - // Convert to GMT |
|
| 930 | - $occstart = $this->toGMT($this->tz, $exception["start"]); |
|
| 931 | - $occend = $this->toGMT($this->tz, $exception["end"]); |
|
| 932 | - |
|
| 933 | - // Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot |
|
| 934 | - // see any part of the appointment. Partial overlaps DO match. |
|
| 935 | - if ($occstart >= $end || $occend <= $start) { |
|
| 936 | - continue; |
|
| 937 | - } |
|
| 938 | - |
|
| 939 | - array_push($items, $this->getExceptionProperties($exception)); |
|
| 940 | - if ((count($items) == $limit)) { |
|
| 941 | - break; |
|
| 942 | - } |
|
| 943 | - } |
|
| 944 | - } |
|
| 945 | - |
|
| 946 | - /** |
|
| 947 | - * Function which verifies if on the given date an exception, delete or change, occurs. |
|
| 948 | - * |
|
| 949 | - * @param date $date the date |
|
| 950 | - * @param mixed $basedate |
|
| 951 | - * |
|
| 952 | - * @return array the exception, true - if an occurrence is deleted on the given date, false - no exception occurs on the given date |
|
| 953 | - */ |
|
| 954 | - public function isException($basedate) { |
|
| 955 | - if ($this->isDeleteException($basedate)) { |
|
| 956 | - return true; |
|
| 957 | - } |
|
| 958 | - |
|
| 959 | - if ($this->getChangeException($basedate) != false) { |
|
| 960 | - return true; |
|
| 961 | - } |
|
| 962 | - |
|
| 963 | - return false; |
|
| 964 | - } |
|
| 965 | - |
|
| 966 | - /** |
|
| 967 | - * Returns TRUE if there is a DELETE exception on the given base date. |
|
| 968 | - * |
|
| 969 | - * @param mixed $basedate |
|
| 970 | - */ |
|
| 971 | - public function isDeleteException($basedate) { |
|
| 972 | - // Check if the occurrence is deleted on the specified date |
|
| 973 | - foreach ($this->recur["deleted_occurrences"] as $deleted) { |
|
| 974 | - if ($this->isSameDay($deleted, $basedate)) { |
|
| 975 | - return true; |
|
| 976 | - } |
|
| 977 | - } |
|
| 978 | - |
|
| 979 | - return false; |
|
| 980 | - } |
|
| 981 | - |
|
| 982 | - /** |
|
| 983 | - * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise. |
|
| 984 | - * |
|
| 985 | - * @param mixed $basedate |
|
| 986 | - */ |
|
| 987 | - public function getChangeException($basedate) { |
|
| 988 | - // Check if the occurrence is modified on the specified date |
|
| 989 | - foreach ($this->recur["changed_occurrences"] as $changed) { |
|
| 990 | - if ($this->isSameDay($changed["basedate"], $basedate)) { |
|
| 991 | - return $changed; |
|
| 992 | - } |
|
| 993 | - } |
|
| 994 | - |
|
| 995 | - return false; |
|
| 996 | - } |
|
| 997 | - |
|
| 998 | - /** |
|
| 999 | - * Function to see if two dates are on the same day. |
|
| 1000 | - * |
|
| 1001 | - * @param date $time1 date 1 |
|
| 1002 | - * @param date $time2 date 2 |
|
| 1003 | - * @param mixed $date1 |
|
| 1004 | - * @param mixed $date2 |
|
| 1005 | - * |
|
| 1006 | - * @return bool Returns TRUE when both dates are on the same day |
|
| 1007 | - */ |
|
| 1008 | - public function isSameDay($date1, $date2) { |
|
| 1009 | - $time1 = $this->gmtime($date1); |
|
| 1010 | - $time2 = $this->gmtime($date2); |
|
| 1011 | - |
|
| 1012 | - return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"]; |
|
| 1013 | - } |
|
| 1014 | - |
|
| 1015 | - /** |
|
| 1016 | - * Function to get all properties of a single changed exception. |
|
| 1017 | - * |
|
| 1018 | - * @param date $date base date of exception |
|
| 1019 | - * @param mixed $exception |
|
| 1020 | - * |
|
| 1021 | - * @return array associative array of properties for the exception, compatible with |
|
| 1022 | - */ |
|
| 1023 | - public function getExceptionProperties($exception) { |
|
| 1024 | - // Exception has same properties as main object, with some properties overridden: |
|
| 1025 | - $item = $this->messageprops; |
|
| 1026 | - |
|
| 1027 | - // Special properties |
|
| 1028 | - $item["exception"] = true; |
|
| 1029 | - $item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time ! |
|
| 1030 | - |
|
| 1031 | - // MAPI-compatible properties (you can handle an exception as a normal calendar item like this) |
|
| 1032 | - $item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]); |
|
| 1033 | - $item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]); |
|
| 1034 | - $item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]]; |
|
| 1035 | - $item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]]; |
|
| 1036 | - |
|
| 1037 | - if (isset($exception["subject"])) { |
|
| 1038 | - $item[$this->proptags["subject"]] = $exception["subject"]; |
|
| 1039 | - } |
|
| 1040 | - |
|
| 1041 | - if (isset($exception["label"])) { |
|
| 1042 | - $item[$this->proptags["label"]] = $exception["label"]; |
|
| 1043 | - } |
|
| 1044 | - |
|
| 1045 | - if (isset($exception["alldayevent"])) { |
|
| 1046 | - $item[$this->proptags["alldayevent"]] = $exception["alldayevent"]; |
|
| 1047 | - } |
|
| 1048 | - |
|
| 1049 | - if (isset($exception["location"])) { |
|
| 1050 | - $item[$this->proptags["location"]] = $exception["location"]; |
|
| 1051 | - } |
|
| 1052 | - |
|
| 1053 | - if (isset($exception["remind_before"])) { |
|
| 1054 | - $item[$this->proptags["reminder_minutes"]] = $exception["remind_before"]; |
|
| 1055 | - } |
|
| 1056 | - |
|
| 1057 | - if (isset($exception["reminder_set"])) { |
|
| 1058 | - $item[$this->proptags["reminder"]] = $exception["reminder_set"]; |
|
| 1059 | - } |
|
| 1060 | - |
|
| 1061 | - if (isset($exception["busystatus"])) { |
|
| 1062 | - $item[$this->proptags["busystatus"]] = $exception["busystatus"]; |
|
| 1063 | - } |
|
| 1064 | - |
|
| 1065 | - return $item; |
|
| 1066 | - } |
|
| 1067 | - |
|
| 1068 | - /** |
|
| 1069 | - * Function which sets recipients for an exception. |
|
| 1070 | - * |
|
| 1071 | - * The $exception_recips can be provided in 2 ways: |
|
| 1072 | - * - A delta which indicates which recipients must be added, removed or deleted. |
|
| 1073 | - * - A complete array of the recipients which should be applied to the message. |
|
| 1074 | - * |
|
| 1075 | - * The first option is preferred as it will require less work to be executed. |
|
| 1076 | - * |
|
| 1077 | - * @param resource $message exception attachment of recurring item |
|
| 1078 | - * @param array $exception_recips list of recipients |
|
| 1079 | - * @param bool $copy_orig_recips True to copy all recipients which are on the original |
|
| 1080 | - * message to the attachment by default. False if only the $exception_recips changes should |
|
| 1081 | - * be applied. |
|
| 1082 | - */ |
|
| 1083 | - public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) { |
|
| 1084 | - if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) { |
|
| 1085 | - $this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips); |
|
| 1086 | - } |
|
| 1087 | - else { |
|
| 1088 | - $this->setAllExceptionRecipients($message, $exception_recips); |
|
| 1089 | - } |
|
| 1090 | - } |
|
| 1091 | - |
|
| 1092 | - /** |
|
| 1093 | - * Function which applies the provided delta for recipients changes to the exception. |
|
| 1094 | - * |
|
| 1095 | - * The $exception_recips should be an array containing the following keys: |
|
| 1096 | - * - "add": this contains an array of recipients which must be added |
|
| 1097 | - * - "remove": This contains an array of recipients which must be removed |
|
| 1098 | - * - "modify": This contains an array of recipients which must be modified |
|
| 1099 | - * |
|
| 1100 | - * @param resource $message exception attachment of recurring item |
|
| 1101 | - * @param array $exception_recips list of recipients |
|
| 1102 | - * @param bool $copy_orig_recips True to copy all recipients which are on the original |
|
| 1103 | - * message to the attachment by default. False if only the $exception_recips changes should |
|
| 1104 | - * be applied. |
|
| 1105 | - * @param mixed $exception |
|
| 1106 | - */ |
|
| 1107 | - public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) { |
|
| 1108 | - // Check if the recipients from the original message should be copied, |
|
| 1109 | - // if so, open the recipient table of the parent message and apply all |
|
| 1110 | - // rows on the target recipient. |
|
| 1111 | - if ($copy_orig_recips === true) { |
|
| 1112 | - $origTable = mapi_message_getrecipienttable($this->message); |
|
| 1113 | - $recipientRows = mapi_table_queryallrows($origTable, $this->recipprops); |
|
| 1114 | - mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows); |
|
| 1115 | - } |
|
| 1116 | - |
|
| 1117 | - // Add organizer to meeting only if it is not organized. |
|
| 1118 | - $msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]); |
|
| 1119 | - if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) { |
|
| 1120 | - $this->addOrganizer($msgprops, $exception_recips['add']); |
|
| 1121 | - } |
|
| 1122 | - |
|
| 1123 | - // Remove all deleted recipients |
|
| 1124 | - if (isset($exception_recips['remove'])) { |
|
| 1125 | - foreach ($exception_recips['remove'] as &$recip) { |
|
| 1126 | - if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
|
| 1127 | - $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
|
| 1128 | - } |
|
| 1129 | - else { |
|
| 1130 | - $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
|
| 1131 | - } |
|
| 1132 | - $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone; // No Response required |
|
| 1133 | - } |
|
| 1134 | - unset($recip); |
|
| 1135 | - mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']); |
|
| 1136 | - } |
|
| 1137 | - |
|
| 1138 | - // Add all new recipients |
|
| 1139 | - if (isset($exception_recips['add'])) { |
|
| 1140 | - mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']); |
|
| 1141 | - } |
|
| 1142 | - |
|
| 1143 | - // Modify the existing recipients |
|
| 1144 | - if (isset($exception_recips['modify'])) { |
|
| 1145 | - mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']); |
|
| 1146 | - } |
|
| 1147 | - } |
|
| 1148 | - |
|
| 1149 | - /** |
|
| 1150 | - * Function which applies the provided recipients to the exception, also checks for deleted recipients. |
|
| 1151 | - * |
|
| 1152 | - * The $exception_recips should be an array containing all recipients which must be applied |
|
| 1153 | - * to the exception. This will copy all recipients from the original message and then start filter |
|
| 1154 | - * out all recipients which are not provided by the $exception_recips list. |
|
| 1155 | - * |
|
| 1156 | - * @param resource $message exception attachment of recurring item |
|
| 1157 | - * @param array $exception_recips list of recipients |
|
| 1158 | - */ |
|
| 1159 | - public function setAllExceptionRecipients($message, $exception_recips) { |
|
| 1160 | - $deletedRecipients = []; |
|
| 1161 | - $useMessageRecipients = false; |
|
| 1162 | - |
|
| 1163 | - $recipientTable = mapi_message_getrecipienttable($message); |
|
| 1164 | - $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 1165 | - |
|
| 1166 | - if (empty($recipientRows)) { |
|
| 1167 | - $useMessageRecipients = true; |
|
| 1168 | - $recipientTable = mapi_message_getrecipienttable($this->message); |
|
| 1169 | - $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 1170 | - } |
|
| 1171 | - |
|
| 1172 | - // Add organizer to meeting only if it is not organized. |
|
| 1173 | - $msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]); |
|
| 1174 | - if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) { |
|
| 1175 | - $this->addOrganizer($msgprops, $exception_recips); |
|
| 1176 | - } |
|
| 1177 | - |
|
| 1178 | - if (!empty($exception_recips)) { |
|
| 1179 | - foreach ($recipientRows as $key => $recipient) { |
|
| 1180 | - $found = false; |
|
| 1181 | - foreach ($exception_recips as $excep_recip) { |
|
| 1182 | - if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) { |
|
| 1183 | - $found = true; |
|
| 1184 | - } |
|
| 1185 | - } |
|
| 1186 | - |
|
| 1187 | - if (!$found) { |
|
| 1188 | - $foundInDeletedRecipients = false; |
|
| 1189 | - // Look if the $recipient is in the list of deleted recipients |
|
| 1190 | - if (!empty($deletedRecipients)) { |
|
| 1191 | - foreach ($deletedRecipients as $recip) { |
|
| 1192 | - if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) { |
|
| 1193 | - $foundInDeletedRecipients = true; |
|
| 1194 | - |
|
| 1195 | - break; |
|
| 1196 | - } |
|
| 1197 | - } |
|
| 1198 | - } |
|
| 1199 | - |
|
| 1200 | - // If recipient is not in list of deleted recipient, add him |
|
| 1201 | - if (!$foundInDeletedRecipients) { |
|
| 1202 | - if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
|
| 1203 | - $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
|
| 1204 | - } |
|
| 1205 | - else { |
|
| 1206 | - $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
|
| 1207 | - } |
|
| 1208 | - $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; // No Response required |
|
| 1209 | - $deletedRecipients[] = $recipient; |
|
| 1210 | - } |
|
| 1211 | - } |
|
| 1212 | - |
|
| 1213 | - // When $message contains a non-empty recipienttable, we must delete the recipients |
|
| 1214 | - // before re-adding them. However, when $message is doesn't contain any recipients, |
|
| 1215 | - // we are using the recipient table of the original message ($this->message) |
|
| 1216 | - // rather then $message. In that case, we don't need to remove the recipients |
|
| 1217 | - // from the $message, as the recipient table is already empty, and |
|
| 1218 | - // mapi_message_modifyrecipients() will throw an error. |
|
| 1219 | - if ($useMessageRecipients === false) { |
|
| 1220 | - mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]); |
|
| 1221 | - } |
|
| 1222 | - } |
|
| 1223 | - $exception_recips = array_merge($exception_recips, $deletedRecipients); |
|
| 1224 | - } |
|
| 1225 | - else { |
|
| 1226 | - $exception_recips = $recipientRows; |
|
| 1227 | - } |
|
| 1228 | - |
|
| 1229 | - if (!empty($exception_recips)) { |
|
| 1230 | - // Set the new list of recipients on the exception message, this also removes the existing recipients |
|
| 1231 | - mapi_message_modifyrecipients($message, 0, $exception_recips); |
|
| 1232 | - } |
|
| 1233 | - } |
|
| 1234 | - |
|
| 1235 | - /** |
|
| 1236 | - * Function returns basedates of all changed occurrences. |
|
| 1237 | - * |
|
| 1238 | - *@return array array( |
|
| 1239 | - * 0 => 123459321 |
|
| 1240 | - * ) |
|
| 1241 | - */ |
|
| 1242 | - public function getAllExceptions() { |
|
| 1243 | - $result = false; |
|
| 1244 | - if (!empty($this->recur["changed_occurrences"])) { |
|
| 1245 | - $result = []; |
|
| 1246 | - foreach ($this->recur["changed_occurrences"] as $exception) { |
|
| 1247 | - $result[] = $exception["basedate"]; |
|
| 1248 | - } |
|
| 1249 | - |
|
| 1250 | - return $result; |
|
| 1251 | - } |
|
| 1252 | - |
|
| 1253 | - return $result; |
|
| 1254 | - } |
|
| 1255 | - |
|
| 1256 | - /** |
|
| 1257 | - * Function which adds organizer to recipient list which is passed. |
|
| 1258 | - * This function also checks if it has organizer. |
|
| 1259 | - * |
|
| 1260 | - * @param array $messageProps message properties |
|
| 1261 | - * @param array $recipients recipients list of message |
|
| 1262 | - * @param bool $isException true if we are processing recipient of exception |
|
| 1263 | - */ |
|
| 1264 | - public function addOrganizer($messageProps, &$recipients, $isException = false) { |
|
| 1265 | - $hasOrganizer = false; |
|
| 1266 | - // Check if meeting already has an organizer. |
|
| 1267 | - foreach ($recipients as $key => $recipient) { |
|
| 1268 | - if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
|
| 1269 | - $hasOrganizer = true; |
|
| 1270 | - } |
|
| 1271 | - elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 1272 | - // Recipients for an occurrence |
|
| 1273 | - $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
|
| 1274 | - } |
|
| 1275 | - } |
|
| 1276 | - |
|
| 1277 | - if (!$hasOrganizer) { |
|
| 1278 | - // Create organizer. |
|
| 1279 | - $organizer = []; |
|
| 1280 | - $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 1281 | - $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 1282 | - $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 1283 | - $organizer[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 1284 | - $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 1285 | - $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 1286 | - $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 1287 | - $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer; |
|
| 1288 | - $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 1289 | - |
|
| 1290 | - // Add organizer to recipients list. |
|
| 1291 | - array_unshift($recipients, $organizer); |
|
| 1292 | - } |
|
| 1293 | - } |
|
| 1294 | - } |
|
| 1295 | - |
|
| 1296 | - /* |
|
| 698 | + public function deleteException($base_date) { |
|
| 699 | + // Remove all exceptions on $base_date from the deleted and changed occurrences lists |
|
| 700 | + |
|
| 701 | + // Remove all items in $todelete from deleted_occurrences |
|
| 702 | + $new = []; |
|
| 703 | + |
|
| 704 | + foreach ($this->recur["deleted_occurrences"] as $entry) { |
|
| 705 | + if ($entry != $base_date) { |
|
| 706 | + $new[] = $entry; |
|
| 707 | + } |
|
| 708 | + } |
|
| 709 | + $this->recur["deleted_occurrences"] = $new; |
|
| 710 | + |
|
| 711 | + $new = []; |
|
| 712 | + |
|
| 713 | + foreach ($this->recur["changed_occurrences"] as $entry) { |
|
| 714 | + if (!$this->isSameDay($entry["basedate"], $base_date)) { |
|
| 715 | + $new[] = $entry; |
|
| 716 | + } |
|
| 717 | + else { |
|
| 718 | + $this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60)); |
|
| 719 | + } |
|
| 720 | + } |
|
| 721 | + |
|
| 722 | + $this->recur["changed_occurrences"] = $new; |
|
| 723 | + } |
|
| 724 | + |
|
| 725 | + /** |
|
| 726 | + * Function which saves the exception data in an attachment. |
|
| 727 | + * |
|
| 728 | + * @param array $exception_props the exception data (like any other MAPI appointment) |
|
| 729 | + * @param array $exception_recips list of recipients |
|
| 730 | + * @param mapi_message $copy_attach_from mapi message from which attachments should be copied |
|
| 731 | + */ |
|
| 732 | + public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) { |
|
| 733 | + // Create new attachment. |
|
| 734 | + $attachment = mapi_message_createattach($this->message); |
|
| 735 | + $props = []; |
|
| 736 | + $props[PR_ATTACHMENT_FLAGS] = 2; |
|
| 737 | + $props[PR_ATTACHMENT_HIDDEN] = true; |
|
| 738 | + $props[PR_ATTACHMENT_LINKID] = 0; |
|
| 739 | + $props[PR_ATTACH_FLAGS] = 0; |
|
| 740 | + $props[PR_ATTACH_METHOD] = 5; |
|
| 741 | + $props[PR_DISPLAY_NAME] = "Exception"; |
|
| 742 | + $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]); |
|
| 743 | + $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
|
| 744 | + mapi_setprops($attachment, $props); |
|
| 745 | + |
|
| 746 | + $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY); |
|
| 747 | + |
|
| 748 | + if ($copy_attach_from) { |
|
| 749 | + $attachmentTable = mapi_message_getattachmenttable($copy_attach_from); |
|
| 750 | + if ($attachmentTable) { |
|
| 751 | + $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
|
| 752 | + |
|
| 753 | + foreach ($attachments as $attach_props) { |
|
| 754 | + $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]); |
|
| 755 | + $attach_newResourceMsg = mapi_message_createattach($imessage); |
|
| 756 | + mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
|
| 757 | + mapi_savechanges($attach_newResourceMsg); |
|
| 758 | + } |
|
| 759 | + } |
|
| 760 | + } |
|
| 761 | + |
|
| 762 | + $props = $props + $exception_props; |
|
| 763 | + |
|
| 764 | + // FIXME: the following piece of code is written to fix the creation |
|
| 765 | + // of an exception. This is only a quickfix as it is not yet possible |
|
| 766 | + // to change an existing exception. |
|
| 767 | + // remove mv properties when needed |
|
| 768 | + foreach ($props as $propTag => $propVal) { |
|
| 769 | + if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)) { |
|
| 770 | + unset($props[$propTag]); |
|
| 771 | + } |
|
| 772 | + } |
|
| 773 | + |
|
| 774 | + mapi_setprops($imessage, $props); |
|
| 775 | + |
|
| 776 | + $this->setExceptionRecipients($imessage, $exception_recips, true); |
|
| 777 | + |
|
| 778 | + mapi_savechanges($imessage); |
|
| 779 | + mapi_savechanges($attachment); |
|
| 780 | + } |
|
| 781 | + |
|
| 782 | + /** |
|
| 783 | + * Function which deletes the attachment of an exception. |
|
| 784 | + * |
|
| 785 | + * @param date $base_date base date of the attachment. Should be in GMT. The attachment |
|
| 786 | + * actually saves the real time of the original date, so we have |
|
| 787 | + * to check whether it's on the same day. |
|
| 788 | + */ |
|
| 789 | + public function deleteExceptionAttachment($base_date) { |
|
| 790 | + $attachments = mapi_message_getattachmenttable($this->message); |
|
| 791 | + $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM]); |
|
| 792 | + |
|
| 793 | + foreach ($attachTable as $attachRow) { |
|
| 794 | + $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 795 | + $exception = mapi_attach_openobj($tempattach); |
|
| 796 | + |
|
| 797 | + $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]); |
|
| 798 | + |
|
| 799 | + if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) { |
|
| 800 | + mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 801 | + } |
|
| 802 | + } |
|
| 803 | + } |
|
| 804 | + |
|
| 805 | + /** |
|
| 806 | + * Function which deletes all attachments of a message. |
|
| 807 | + */ |
|
| 808 | + public function deleteAttachments() { |
|
| 809 | + $attachments = mapi_message_getattachmenttable($this->message); |
|
| 810 | + $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]); |
|
| 811 | + |
|
| 812 | + foreach ($attachTable as $attachRow) { |
|
| 813 | + if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) { |
|
| 814 | + mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 815 | + } |
|
| 816 | + } |
|
| 817 | + } |
|
| 818 | + |
|
| 819 | + /** |
|
| 820 | + * Get an exception attachment based on its basedate. |
|
| 821 | + * |
|
| 822 | + * @param mixed $base_date |
|
| 823 | + */ |
|
| 824 | + public function getExceptionAttachment($base_date) { |
|
| 825 | + // Retrieve only exceptions which are stored as embedded messages |
|
| 826 | + $attach_res = [ |
|
| 827 | + RES_AND, |
|
| 828 | + [ |
|
| 829 | + [ |
|
| 830 | + RES_PROPERTY, |
|
| 831 | + [ |
|
| 832 | + RELOP => RELOP_EQ, |
|
| 833 | + ULPROPTAG => PR_ATTACH_METHOD, |
|
| 834 | + VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG], |
|
| 835 | + ], |
|
| 836 | + ], |
|
| 837 | + ], |
|
| 838 | + ]; |
|
| 839 | + $attachments = mapi_message_getattachmenttable($this->message); |
|
| 840 | + $attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res); |
|
| 841 | + |
|
| 842 | + if (is_array($attachRows)) { |
|
| 843 | + foreach ($attachRows as $attachRow) { |
|
| 844 | + $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]); |
|
| 845 | + $exception = mapi_attach_openobj($tempattach); |
|
| 846 | + |
|
| 847 | + $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]); |
|
| 848 | + |
|
| 849 | + if (!isset($data[$this->proptags["basedate"]])) { |
|
| 850 | + // if no basedate found then it could be embedded message so ignore it |
|
| 851 | + // we need proper restriction to exclude embedded messages as well |
|
| 852 | + continue; |
|
| 853 | + } |
|
| 854 | + |
|
| 855 | + if ($this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) { |
|
| 856 | + return $tempattach; |
|
| 857 | + } |
|
| 858 | + } |
|
| 859 | + } |
|
| 860 | + |
|
| 861 | + return false; |
|
| 862 | + } |
|
| 863 | + |
|
| 864 | + /** |
|
| 865 | + * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met: |
|
| 866 | + * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end> |
|
| 867 | + * - The occurrence isn't specified as a deleted occurrence. |
|
| 868 | + * |
|
| 869 | + * @param array $items reference to the array to be added to |
|
| 870 | + * @param date $start start of timeframe in GMT TIME |
|
| 871 | + * @param date $end end of timeframe in GMT TIME |
|
| 872 | + * @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE |
|
| 873 | + * @param int $startocc start of occurrence since beginning of day in minutes |
|
| 874 | + * @param int $endocc end of occurrence since beginning of day in minutes |
|
| 875 | + * @param int $tz the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc ) |
|
| 876 | + * @param bool $reminderonly If TRUE, only add the item if the reminder is set |
|
| 877 | + */ |
|
| 878 | + public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) { |
|
| 879 | + $exception = $this->isException($basedate); |
|
| 880 | + if ($exception) { |
|
| 881 | + return false; |
|
| 882 | + } |
|
| 883 | + $occstart = $basedate + $startocc * 60; |
|
| 884 | + $occend = $basedate + $endocc * 60; |
|
| 885 | + |
|
| 886 | + // Convert to GMT |
|
| 887 | + $occstart = $this->toGMT($tz, $occstart); |
|
| 888 | + $occend = $this->toGMT($tz, $occend); |
|
| 889 | + |
|
| 890 | + /** |
|
| 891 | + * FIRST PART : Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot |
|
| 892 | + * see any part of the appointment. Partial overlaps DO match. |
|
| 893 | + * |
|
| 894 | + * SECOND PART : check if occurrence is not a zero duration occurrence which |
|
| 895 | + * starts at 00:00 and ends on 00:00. if it is so, then process |
|
| 896 | + * the occurrence and send it in response. |
|
| 897 | + */ |
|
| 898 | + if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) { |
|
| 899 | + return; |
|
| 900 | + } |
|
| 901 | + |
|
| 902 | + // Properties for this occurrence are the same as the main object, |
|
| 903 | + // With these properties overridden |
|
| 904 | + $newitem = $this->messageprops; |
|
| 905 | + $newitem[$this->proptags["startdate"]] = $occstart; |
|
| 906 | + $newitem[$this->proptags["duedate"]] = $occend; |
|
| 907 | + $newitem[$this->proptags["commonstart"]] = $occstart; |
|
| 908 | + $newitem[$this->proptags["commonend"]] = $occend; |
|
| 909 | + $newitem["basedate"] = $basedate; |
|
| 910 | + |
|
| 911 | + // If reminderonly is set, only add reminders |
|
| 912 | + if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) { |
|
| 913 | + return; |
|
| 914 | + } |
|
| 915 | + |
|
| 916 | + $items[] = $newitem; |
|
| 917 | + } |
|
| 918 | + |
|
| 919 | + /** |
|
| 920 | + * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe. |
|
| 921 | + * |
|
| 922 | + * @param array $items reference to the array to be added to |
|
| 923 | + * @param date $start start of timeframe in GMT TIME |
|
| 924 | + * @param date $end end of timeframe in GMT TIME |
|
| 925 | + */ |
|
| 926 | + public function processExceptionItems(&$items, $start, $end) { |
|
| 927 | + $limit = 0; |
|
| 928 | + foreach ($this->recur["changed_occurrences"] as $exception) { |
|
| 929 | + // Convert to GMT |
|
| 930 | + $occstart = $this->toGMT($this->tz, $exception["start"]); |
|
| 931 | + $occend = $this->toGMT($this->tz, $exception["end"]); |
|
| 932 | + |
|
| 933 | + // Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot |
|
| 934 | + // see any part of the appointment. Partial overlaps DO match. |
|
| 935 | + if ($occstart >= $end || $occend <= $start) { |
|
| 936 | + continue; |
|
| 937 | + } |
|
| 938 | + |
|
| 939 | + array_push($items, $this->getExceptionProperties($exception)); |
|
| 940 | + if ((count($items) == $limit)) { |
|
| 941 | + break; |
|
| 942 | + } |
|
| 943 | + } |
|
| 944 | + } |
|
| 945 | + |
|
| 946 | + /** |
|
| 947 | + * Function which verifies if on the given date an exception, delete or change, occurs. |
|
| 948 | + * |
|
| 949 | + * @param date $date the date |
|
| 950 | + * @param mixed $basedate |
|
| 951 | + * |
|
| 952 | + * @return array the exception, true - if an occurrence is deleted on the given date, false - no exception occurs on the given date |
|
| 953 | + */ |
|
| 954 | + public function isException($basedate) { |
|
| 955 | + if ($this->isDeleteException($basedate)) { |
|
| 956 | + return true; |
|
| 957 | + } |
|
| 958 | + |
|
| 959 | + if ($this->getChangeException($basedate) != false) { |
|
| 960 | + return true; |
|
| 961 | + } |
|
| 962 | + |
|
| 963 | + return false; |
|
| 964 | + } |
|
| 965 | + |
|
| 966 | + /** |
|
| 967 | + * Returns TRUE if there is a DELETE exception on the given base date. |
|
| 968 | + * |
|
| 969 | + * @param mixed $basedate |
|
| 970 | + */ |
|
| 971 | + public function isDeleteException($basedate) { |
|
| 972 | + // Check if the occurrence is deleted on the specified date |
|
| 973 | + foreach ($this->recur["deleted_occurrences"] as $deleted) { |
|
| 974 | + if ($this->isSameDay($deleted, $basedate)) { |
|
| 975 | + return true; |
|
| 976 | + } |
|
| 977 | + } |
|
| 978 | + |
|
| 979 | + return false; |
|
| 980 | + } |
|
| 981 | + |
|
| 982 | + /** |
|
| 983 | + * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise. |
|
| 984 | + * |
|
| 985 | + * @param mixed $basedate |
|
| 986 | + */ |
|
| 987 | + public function getChangeException($basedate) { |
|
| 988 | + // Check if the occurrence is modified on the specified date |
|
| 989 | + foreach ($this->recur["changed_occurrences"] as $changed) { |
|
| 990 | + if ($this->isSameDay($changed["basedate"], $basedate)) { |
|
| 991 | + return $changed; |
|
| 992 | + } |
|
| 993 | + } |
|
| 994 | + |
|
| 995 | + return false; |
|
| 996 | + } |
|
| 997 | + |
|
| 998 | + /** |
|
| 999 | + * Function to see if two dates are on the same day. |
|
| 1000 | + * |
|
| 1001 | + * @param date $time1 date 1 |
|
| 1002 | + * @param date $time2 date 2 |
|
| 1003 | + * @param mixed $date1 |
|
| 1004 | + * @param mixed $date2 |
|
| 1005 | + * |
|
| 1006 | + * @return bool Returns TRUE when both dates are on the same day |
|
| 1007 | + */ |
|
| 1008 | + public function isSameDay($date1, $date2) { |
|
| 1009 | + $time1 = $this->gmtime($date1); |
|
| 1010 | + $time2 = $this->gmtime($date2); |
|
| 1011 | + |
|
| 1012 | + return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"]; |
|
| 1013 | + } |
|
| 1014 | + |
|
| 1015 | + /** |
|
| 1016 | + * Function to get all properties of a single changed exception. |
|
| 1017 | + * |
|
| 1018 | + * @param date $date base date of exception |
|
| 1019 | + * @param mixed $exception |
|
| 1020 | + * |
|
| 1021 | + * @return array associative array of properties for the exception, compatible with |
|
| 1022 | + */ |
|
| 1023 | + public function getExceptionProperties($exception) { |
|
| 1024 | + // Exception has same properties as main object, with some properties overridden: |
|
| 1025 | + $item = $this->messageprops; |
|
| 1026 | + |
|
| 1027 | + // Special properties |
|
| 1028 | + $item["exception"] = true; |
|
| 1029 | + $item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time ! |
|
| 1030 | + |
|
| 1031 | + // MAPI-compatible properties (you can handle an exception as a normal calendar item like this) |
|
| 1032 | + $item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]); |
|
| 1033 | + $item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]); |
|
| 1034 | + $item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]]; |
|
| 1035 | + $item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]]; |
|
| 1036 | + |
|
| 1037 | + if (isset($exception["subject"])) { |
|
| 1038 | + $item[$this->proptags["subject"]] = $exception["subject"]; |
|
| 1039 | + } |
|
| 1040 | + |
|
| 1041 | + if (isset($exception["label"])) { |
|
| 1042 | + $item[$this->proptags["label"]] = $exception["label"]; |
|
| 1043 | + } |
|
| 1044 | + |
|
| 1045 | + if (isset($exception["alldayevent"])) { |
|
| 1046 | + $item[$this->proptags["alldayevent"]] = $exception["alldayevent"]; |
|
| 1047 | + } |
|
| 1048 | + |
|
| 1049 | + if (isset($exception["location"])) { |
|
| 1050 | + $item[$this->proptags["location"]] = $exception["location"]; |
|
| 1051 | + } |
|
| 1052 | + |
|
| 1053 | + if (isset($exception["remind_before"])) { |
|
| 1054 | + $item[$this->proptags["reminder_minutes"]] = $exception["remind_before"]; |
|
| 1055 | + } |
|
| 1056 | + |
|
| 1057 | + if (isset($exception["reminder_set"])) { |
|
| 1058 | + $item[$this->proptags["reminder"]] = $exception["reminder_set"]; |
|
| 1059 | + } |
|
| 1060 | + |
|
| 1061 | + if (isset($exception["busystatus"])) { |
|
| 1062 | + $item[$this->proptags["busystatus"]] = $exception["busystatus"]; |
|
| 1063 | + } |
|
| 1064 | + |
|
| 1065 | + return $item; |
|
| 1066 | + } |
|
| 1067 | + |
|
| 1068 | + /** |
|
| 1069 | + * Function which sets recipients for an exception. |
|
| 1070 | + * |
|
| 1071 | + * The $exception_recips can be provided in 2 ways: |
|
| 1072 | + * - A delta which indicates which recipients must be added, removed or deleted. |
|
| 1073 | + * - A complete array of the recipients which should be applied to the message. |
|
| 1074 | + * |
|
| 1075 | + * The first option is preferred as it will require less work to be executed. |
|
| 1076 | + * |
|
| 1077 | + * @param resource $message exception attachment of recurring item |
|
| 1078 | + * @param array $exception_recips list of recipients |
|
| 1079 | + * @param bool $copy_orig_recips True to copy all recipients which are on the original |
|
| 1080 | + * message to the attachment by default. False if only the $exception_recips changes should |
|
| 1081 | + * be applied. |
|
| 1082 | + */ |
|
| 1083 | + public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) { |
|
| 1084 | + if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) { |
|
| 1085 | + $this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips); |
|
| 1086 | + } |
|
| 1087 | + else { |
|
| 1088 | + $this->setAllExceptionRecipients($message, $exception_recips); |
|
| 1089 | + } |
|
| 1090 | + } |
|
| 1091 | + |
|
| 1092 | + /** |
|
| 1093 | + * Function which applies the provided delta for recipients changes to the exception. |
|
| 1094 | + * |
|
| 1095 | + * The $exception_recips should be an array containing the following keys: |
|
| 1096 | + * - "add": this contains an array of recipients which must be added |
|
| 1097 | + * - "remove": This contains an array of recipients which must be removed |
|
| 1098 | + * - "modify": This contains an array of recipients which must be modified |
|
| 1099 | + * |
|
| 1100 | + * @param resource $message exception attachment of recurring item |
|
| 1101 | + * @param array $exception_recips list of recipients |
|
| 1102 | + * @param bool $copy_orig_recips True to copy all recipients which are on the original |
|
| 1103 | + * message to the attachment by default. False if only the $exception_recips changes should |
|
| 1104 | + * be applied. |
|
| 1105 | + * @param mixed $exception |
|
| 1106 | + */ |
|
| 1107 | + public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) { |
|
| 1108 | + // Check if the recipients from the original message should be copied, |
|
| 1109 | + // if so, open the recipient table of the parent message and apply all |
|
| 1110 | + // rows on the target recipient. |
|
| 1111 | + if ($copy_orig_recips === true) { |
|
| 1112 | + $origTable = mapi_message_getrecipienttable($this->message); |
|
| 1113 | + $recipientRows = mapi_table_queryallrows($origTable, $this->recipprops); |
|
| 1114 | + mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows); |
|
| 1115 | + } |
|
| 1116 | + |
|
| 1117 | + // Add organizer to meeting only if it is not organized. |
|
| 1118 | + $msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]); |
|
| 1119 | + if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) { |
|
| 1120 | + $this->addOrganizer($msgprops, $exception_recips['add']); |
|
| 1121 | + } |
|
| 1122 | + |
|
| 1123 | + // Remove all deleted recipients |
|
| 1124 | + if (isset($exception_recips['remove'])) { |
|
| 1125 | + foreach ($exception_recips['remove'] as &$recip) { |
|
| 1126 | + if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
|
| 1127 | + $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
|
| 1128 | + } |
|
| 1129 | + else { |
|
| 1130 | + $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
|
| 1131 | + } |
|
| 1132 | + $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone; // No Response required |
|
| 1133 | + } |
|
| 1134 | + unset($recip); |
|
| 1135 | + mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']); |
|
| 1136 | + } |
|
| 1137 | + |
|
| 1138 | + // Add all new recipients |
|
| 1139 | + if (isset($exception_recips['add'])) { |
|
| 1140 | + mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']); |
|
| 1141 | + } |
|
| 1142 | + |
|
| 1143 | + // Modify the existing recipients |
|
| 1144 | + if (isset($exception_recips['modify'])) { |
|
| 1145 | + mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']); |
|
| 1146 | + } |
|
| 1147 | + } |
|
| 1148 | + |
|
| 1149 | + /** |
|
| 1150 | + * Function which applies the provided recipients to the exception, also checks for deleted recipients. |
|
| 1151 | + * |
|
| 1152 | + * The $exception_recips should be an array containing all recipients which must be applied |
|
| 1153 | + * to the exception. This will copy all recipients from the original message and then start filter |
|
| 1154 | + * out all recipients which are not provided by the $exception_recips list. |
|
| 1155 | + * |
|
| 1156 | + * @param resource $message exception attachment of recurring item |
|
| 1157 | + * @param array $exception_recips list of recipients |
|
| 1158 | + */ |
|
| 1159 | + public function setAllExceptionRecipients($message, $exception_recips) { |
|
| 1160 | + $deletedRecipients = []; |
|
| 1161 | + $useMessageRecipients = false; |
|
| 1162 | + |
|
| 1163 | + $recipientTable = mapi_message_getrecipienttable($message); |
|
| 1164 | + $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 1165 | + |
|
| 1166 | + if (empty($recipientRows)) { |
|
| 1167 | + $useMessageRecipients = true; |
|
| 1168 | + $recipientTable = mapi_message_getrecipienttable($this->message); |
|
| 1169 | + $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops); |
|
| 1170 | + } |
|
| 1171 | + |
|
| 1172 | + // Add organizer to meeting only if it is not organized. |
|
| 1173 | + $msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]); |
|
| 1174 | + if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) { |
|
| 1175 | + $this->addOrganizer($msgprops, $exception_recips); |
|
| 1176 | + } |
|
| 1177 | + |
|
| 1178 | + if (!empty($exception_recips)) { |
|
| 1179 | + foreach ($recipientRows as $key => $recipient) { |
|
| 1180 | + $found = false; |
|
| 1181 | + foreach ($exception_recips as $excep_recip) { |
|
| 1182 | + if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) { |
|
| 1183 | + $found = true; |
|
| 1184 | + } |
|
| 1185 | + } |
|
| 1186 | + |
|
| 1187 | + if (!$found) { |
|
| 1188 | + $foundInDeletedRecipients = false; |
|
| 1189 | + // Look if the $recipient is in the list of deleted recipients |
|
| 1190 | + if (!empty($deletedRecipients)) { |
|
| 1191 | + foreach ($deletedRecipients as $recip) { |
|
| 1192 | + if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) { |
|
| 1193 | + $foundInDeletedRecipients = true; |
|
| 1194 | + |
|
| 1195 | + break; |
|
| 1196 | + } |
|
| 1197 | + } |
|
| 1198 | + } |
|
| 1199 | + |
|
| 1200 | + // If recipient is not in list of deleted recipient, add him |
|
| 1201 | + if (!$foundInDeletedRecipients) { |
|
| 1202 | + if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
|
| 1203 | + $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
|
| 1204 | + } |
|
| 1205 | + else { |
|
| 1206 | + $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
|
| 1207 | + } |
|
| 1208 | + $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; // No Response required |
|
| 1209 | + $deletedRecipients[] = $recipient; |
|
| 1210 | + } |
|
| 1211 | + } |
|
| 1212 | + |
|
| 1213 | + // When $message contains a non-empty recipienttable, we must delete the recipients |
|
| 1214 | + // before re-adding them. However, when $message is doesn't contain any recipients, |
|
| 1215 | + // we are using the recipient table of the original message ($this->message) |
|
| 1216 | + // rather then $message. In that case, we don't need to remove the recipients |
|
| 1217 | + // from the $message, as the recipient table is already empty, and |
|
| 1218 | + // mapi_message_modifyrecipients() will throw an error. |
|
| 1219 | + if ($useMessageRecipients === false) { |
|
| 1220 | + mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]); |
|
| 1221 | + } |
|
| 1222 | + } |
|
| 1223 | + $exception_recips = array_merge($exception_recips, $deletedRecipients); |
|
| 1224 | + } |
|
| 1225 | + else { |
|
| 1226 | + $exception_recips = $recipientRows; |
|
| 1227 | + } |
|
| 1228 | + |
|
| 1229 | + if (!empty($exception_recips)) { |
|
| 1230 | + // Set the new list of recipients on the exception message, this also removes the existing recipients |
|
| 1231 | + mapi_message_modifyrecipients($message, 0, $exception_recips); |
|
| 1232 | + } |
|
| 1233 | + } |
|
| 1234 | + |
|
| 1235 | + /** |
|
| 1236 | + * Function returns basedates of all changed occurrences. |
|
| 1237 | + * |
|
| 1238 | + *@return array array( |
|
| 1239 | + * 0 => 123459321 |
|
| 1240 | + * ) |
|
| 1241 | + */ |
|
| 1242 | + public function getAllExceptions() { |
|
| 1243 | + $result = false; |
|
| 1244 | + if (!empty($this->recur["changed_occurrences"])) { |
|
| 1245 | + $result = []; |
|
| 1246 | + foreach ($this->recur["changed_occurrences"] as $exception) { |
|
| 1247 | + $result[] = $exception["basedate"]; |
|
| 1248 | + } |
|
| 1249 | + |
|
| 1250 | + return $result; |
|
| 1251 | + } |
|
| 1252 | + |
|
| 1253 | + return $result; |
|
| 1254 | + } |
|
| 1255 | + |
|
| 1256 | + /** |
|
| 1257 | + * Function which adds organizer to recipient list which is passed. |
|
| 1258 | + * This function also checks if it has organizer. |
|
| 1259 | + * |
|
| 1260 | + * @param array $messageProps message properties |
|
| 1261 | + * @param array $recipients recipients list of message |
|
| 1262 | + * @param bool $isException true if we are processing recipient of exception |
|
| 1263 | + */ |
|
| 1264 | + public function addOrganizer($messageProps, &$recipients, $isException = false) { |
|
| 1265 | + $hasOrganizer = false; |
|
| 1266 | + // Check if meeting already has an organizer. |
|
| 1267 | + foreach ($recipients as $key => $recipient) { |
|
| 1268 | + if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
|
| 1269 | + $hasOrganizer = true; |
|
| 1270 | + } |
|
| 1271 | + elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 1272 | + // Recipients for an occurrence |
|
| 1273 | + $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
|
| 1274 | + } |
|
| 1275 | + } |
|
| 1276 | + |
|
| 1277 | + if (!$hasOrganizer) { |
|
| 1278 | + // Create organizer. |
|
| 1279 | + $organizer = []; |
|
| 1280 | + $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID]; |
|
| 1281 | + $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 1282 | + $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS]; |
|
| 1283 | + $organizer[PR_RECIPIENT_TYPE] = MAPI_TO; |
|
| 1284 | + $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
|
| 1285 | + $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE]; |
|
| 1286 | + $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
|
| 1287 | + $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer; |
|
| 1288 | + $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY]; |
|
| 1289 | + |
|
| 1290 | + // Add organizer to recipients list. |
|
| 1291 | + array_unshift($recipients, $organizer); |
|
| 1292 | + } |
|
| 1293 | + } |
|
| 1294 | + } |
|
| 1295 | + |
|
| 1296 | + /* |
|
| 1297 | 1297 | |
| 1298 | 1298 | From http://www.ohelp-one.com/new-6765483-3268.html: |
| 1299 | 1299 | |
@@ -598,8 +598,8 @@ discard block |
||
| 598 | 598 | } |
| 599 | 599 | |
| 600 | 600 | // get timings of the first occurrence |
| 601 | - $firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start; |
|
| 602 | - $firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end; |
|
| 601 | + $firstoccstartdate = isset($startocc) ? $start + (((int)$startocc) * 60) : $start; |
|
| 602 | + $firstoccenddate = isset($endocc) ? $end + (((int)$endocc) * 60) : $end; |
|
| 603 | 603 | |
| 604 | 604 | $start = gmdate(_('d-m-Y'), $firstoccstartdate); |
| 605 | 605 | $end = gmdate(_('d-m-Y'), $firstoccenddate); |
@@ -743,7 +743,7 @@ discard block |
||
| 743 | 743 | $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]); |
| 744 | 744 | mapi_setprops($attachment, $props); |
| 745 | 745 | |
| 746 | - $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY); |
|
| 746 | + $imessage = mapi_attach_openobj($attachment, MAPI_CREATE|MAPI_MODIFY); |
|
| 747 | 747 | |
| 748 | 748 | if ($copy_attach_from) { |
| 749 | 749 | $attachmentTable = mapi_message_getattachmenttable($copy_attach_from); |
@@ -751,7 +751,7 @@ discard block |
||
| 751 | 751 | $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]); |
| 752 | 752 | |
| 753 | 753 | foreach ($attachments as $attach_props) { |
| 754 | - $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]); |
|
| 754 | + $attach_old = mapi_message_openattach($copy_attach_from, (int)$attach_props[PR_ATTACH_NUM]); |
|
| 755 | 755 | $attach_newResourceMsg = mapi_message_createattach($imessage); |
| 756 | 756 | mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0); |
| 757 | 757 | mapi_savechanges($attach_newResourceMsg); |
@@ -1123,13 +1123,13 @@ discard block |
||
| 1123 | 1123 | // Remove all deleted recipients |
| 1124 | 1124 | if (isset($exception_recips['remove'])) { |
| 1125 | 1125 | foreach ($exception_recips['remove'] as &$recip) { |
| 1126 | - if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
|
| 1127 | - $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
|
| 1126 | + if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved|recipExceptionalDeleted|recipSendable)) { |
|
| 1127 | + $recip[PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalDeleted; |
|
| 1128 | 1128 | } |
| 1129 | 1129 | else { |
| 1130 | - $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
|
| 1130 | + $recip[PR_RECIPIENT_FLAGS] = recipReserved|recipExceptionalDeleted|recipSendable; |
|
| 1131 | 1131 | } |
| 1132 | - $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone; // No Response required |
|
| 1132 | + $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone; // No Response required |
|
| 1133 | 1133 | } |
| 1134 | 1134 | unset($recip); |
| 1135 | 1135 | mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']); |
@@ -1199,13 +1199,13 @@ discard block |
||
| 1199 | 1199 | |
| 1200 | 1200 | // If recipient is not in list of deleted recipient, add him |
| 1201 | 1201 | if (!$foundInDeletedRecipients) { |
| 1202 | - if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
|
| 1203 | - $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
|
| 1202 | + if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved|recipExceptionalDeleted|recipSendable)) { |
|
| 1203 | + $recipient[PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalDeleted; |
|
| 1204 | 1204 | } |
| 1205 | 1205 | else { |
| 1206 | - $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
|
| 1206 | + $recipient[PR_RECIPIENT_FLAGS] = recipReserved|recipExceptionalDeleted|recipSendable; |
|
| 1207 | 1207 | } |
| 1208 | - $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; // No Response required |
|
| 1208 | + $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; // No Response required |
|
| 1209 | 1209 | $deletedRecipients[] = $recipient; |
| 1210 | 1210 | } |
| 1211 | 1211 | } |
@@ -1265,12 +1265,12 @@ discard block |
||
| 1265 | 1265 | $hasOrganizer = false; |
| 1266 | 1266 | // Check if meeting already has an organizer. |
| 1267 | 1267 | foreach ($recipients as $key => $recipient) { |
| 1268 | - if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
|
| 1268 | + if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable|recipOrganizer)) { |
|
| 1269 | 1269 | $hasOrganizer = true; |
| 1270 | 1270 | } |
| 1271 | 1271 | elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
| 1272 | 1272 | // Recipients for an occurrence |
| 1273 | - $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
|
| 1273 | + $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable|recipExceptionalResponse; |
|
| 1274 | 1274 | } |
| 1275 | 1275 | } |
| 1276 | 1276 | |
@@ -1284,7 +1284,7 @@ discard block |
||
| 1284 | 1284 | $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME]; |
| 1285 | 1285 | $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE]; |
| 1286 | 1286 | $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; |
| 1287 | - $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer; |
|
| 1287 | + $organizer[PR_RECIPIENT_FLAGS] = recipSendable|recipOrganizer; |
|
| 1288 | 1288 | $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY]; |
| 1289 | 1289 | |
| 1290 | 1290 | // Add organizer to recipients list. |
@@ -51,8 +51,7 @@ discard block |
||
| 51 | 51 | public function __construct($store, $message, $proptags = []) { |
| 52 | 52 | if ($proptags) { |
| 53 | 53 | $this->proptags = $proptags; |
| 54 | - } |
|
| 55 | - else { |
|
| 54 | + } else { |
|
| 56 | 55 | $properties = []; |
| 57 | 56 | $properties["entryid"] = PR_ENTRYID; |
| 58 | 57 | $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
@@ -192,8 +191,7 @@ discard block |
||
| 192 | 191 | |
| 193 | 192 | // Add the changed occurrence to the list |
| 194 | 193 | array_push($this->recur["changed_occurrences"], $changed_item); |
| 195 | - } |
|
| 196 | - else { |
|
| 194 | + } else { |
|
| 197 | 195 | // Delete the occurrence by placing it in the deleted occurrences list |
| 198 | 196 | array_push($this->recur["deleted_occurrences"], $baseday); |
| 199 | 197 | } |
@@ -290,12 +288,10 @@ discard block |
||
| 290 | 288 | if ($copy_attach_from) { |
| 291 | 289 | $this->deleteExceptionAttachment($base_date); |
| 292 | 290 | $this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from); |
| 293 | - } |
|
| 294 | - else { |
|
| 291 | + } else { |
|
| 295 | 292 | $this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from); |
| 296 | 293 | } |
| 297 | - } |
|
| 298 | - else { |
|
| 294 | + } else { |
|
| 299 | 295 | $message = mapi_attach_openobj($attach, MAPI_MODIFY); |
| 300 | 296 | |
| 301 | 297 | // Set exception properties on embedded message and save |
@@ -339,8 +335,7 @@ discard block |
||
| 339 | 335 | // the exception used to be. |
| 340 | 336 | $oldexception = $this->getChangeException($basedate); |
| 341 | 337 | $prevday = $this->dayStartOf($oldexception["start"]); |
| 342 | - } |
|
| 343 | - else { |
|
| 338 | + } else { |
|
| 344 | 339 | // If its a new exception, we want to look at the original placement of this item. |
| 345 | 340 | $prevday = $basedate; |
| 346 | 341 | } |
@@ -350,8 +345,7 @@ discard block |
||
| 350 | 345 | // Get all the occurrences on the days between the basedate (may be reversed) |
| 351 | 346 | if ($prevday < $startday) { |
| 352 | 347 | $items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60)); |
| 353 | - } |
|
| 354 | - else { |
|
| 348 | + } else { |
|
| 355 | 349 | $items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60)); |
| 356 | 350 | } |
| 357 | 351 | |
@@ -545,12 +539,10 @@ discard block |
||
| 545 | 539 | if ($everyn == 1) { |
| 546 | 540 | $type = _('workday'); |
| 547 | 541 | $occSingleDayRank = true; |
| 548 | - } |
|
| 549 | - elseif ($everyn == (24 * 60)) { |
|
| 542 | + } elseif ($everyn == (24 * 60)) { |
|
| 550 | 543 | $type = _('day'); |
| 551 | 544 | $occSingleDayRank = true; |
| 552 | - } |
|
| 553 | - else { |
|
| 545 | + } else { |
|
| 554 | 546 | $everyn /= (24 * 60); |
| 555 | 547 | $type = _('days'); |
| 556 | 548 | $occSingleDayRank = false; |
@@ -562,8 +554,7 @@ discard block |
||
| 562 | 554 | if ($everyn == 1) { |
| 563 | 555 | $type = _('week'); |
| 564 | 556 | $occSingleDayRank = true; |
| 565 | - } |
|
| 566 | - else { |
|
| 557 | + } else { |
|
| 567 | 558 | $type = _('weeks'); |
| 568 | 559 | $occSingleDayRank = false; |
| 569 | 560 | } |
@@ -574,8 +565,7 @@ discard block |
||
| 574 | 565 | if ($everyn == 1) { |
| 575 | 566 | $type = _('month'); |
| 576 | 567 | $occSingleDayRank = true; |
| 577 | - } |
|
| 578 | - else { |
|
| 568 | + } else { |
|
| 579 | 569 | $type = _('months'); |
| 580 | 570 | $occSingleDayRank = false; |
| 581 | 571 | } |
@@ -587,8 +577,7 @@ discard block |
||
| 587 | 577 | $everyn = 1; |
| 588 | 578 | $type = _('year'); |
| 589 | 579 | $occSingleDayRank = true; |
| 590 | - } |
|
| 591 | - else { |
|
| 580 | + } else { |
|
| 592 | 581 | $everyn = $everyn / 12; |
| 593 | 582 | $type = _('years'); |
| 594 | 583 | $occSingleDayRank = false; |
@@ -618,21 +607,17 @@ discard block |
||
| 618 | 607 | if ($occTimeRange) { |
| 619 | 608 | if ($occSingleDayRank) { |
| 620 | 609 | $pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc); |
| 621 | - } |
|
| 622 | - else { |
|
| 610 | + } else { |
|
| 623 | 611 | $pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc); |
| 624 | 612 | } |
| 625 | - } |
|
| 626 | - else { |
|
| 613 | + } else { |
|
| 627 | 614 | if ($occSingleDayRank) { |
| 628 | 615 | $pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start); |
| 629 | - } |
|
| 630 | - else { |
|
| 616 | + } else { |
|
| 631 | 617 | $pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start); |
| 632 | 618 | } |
| 633 | 619 | } |
| 634 | - } |
|
| 635 | - elseif ($term == 0x22) { |
|
| 620 | + } elseif ($term == 0x22) { |
|
| 636 | 621 | // After a number of times |
| 637 | 622 | if ($occTimeRange) { |
| 638 | 623 | if ($occSingleDayRank) { |
@@ -641,24 +626,21 @@ discard block |
||
| 641 | 626 | 'Occurs every %s effective %s for %s occurrences from %s to %s.', |
| 642 | 627 | $numocc |
| 643 | 628 | ), $type, $start, $numocc, $startocc, $endocc); |
| 644 | - } |
|
| 645 | - else { |
|
| 629 | + } else { |
|
| 646 | 630 | $pattern = sprintf(ngettext( |
| 647 | 631 | 'Occurs every %s %s effective %s for %s occurrence from %s to %s.', |
| 648 | 632 | 'Occurs every %s %s effective %s for %s occurrences %s to %s.', |
| 649 | 633 | $numocc |
| 650 | 634 | ), $everyn, $type, $start, $numocc, $startocc, $endocc); |
| 651 | 635 | } |
| 652 | - } |
|
| 653 | - else { |
|
| 636 | + } else { |
|
| 654 | 637 | if ($occSingleDayRank) { |
| 655 | 638 | $pattern = sprintf(ngettext( |
| 656 | 639 | 'Occurs every %s effective %s for %s occurrence.', |
| 657 | 640 | 'Occurs every %s effective %s for %s occurrences.', |
| 658 | 641 | $numocc |
| 659 | 642 | ), $type, $start, $numocc); |
| 660 | - } |
|
| 661 | - else { |
|
| 643 | + } else { |
|
| 662 | 644 | $pattern = sprintf(ngettext( |
| 663 | 645 | 'Occurs every %s %s effective %s for %s occurrence.', |
| 664 | 646 | 'Occurs every %s %s effective %s for %s occurrences.', |
@@ -666,22 +648,18 @@ discard block |
||
| 666 | 648 | ), $everyn, $type, $start, $numocc); |
| 667 | 649 | } |
| 668 | 650 | } |
| 669 | - } |
|
| 670 | - elseif ($term == 0x21) { |
|
| 651 | + } elseif ($term == 0x21) { |
|
| 671 | 652 | // After the given enddate |
| 672 | 653 | if ($occTimeRange) { |
| 673 | 654 | if ($occSingleDayRank) { |
| 674 | 655 | $pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc); |
| 675 | - } |
|
| 676 | - else { |
|
| 656 | + } else { |
|
| 677 | 657 | $pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc); |
| 678 | 658 | } |
| 679 | - } |
|
| 680 | - else { |
|
| 659 | + } else { |
|
| 681 | 660 | if ($occSingleDayRank) { |
| 682 | 661 | $pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end); |
| 683 | - } |
|
| 684 | - else { |
|
| 662 | + } else { |
|
| 685 | 663 | $pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end); |
| 686 | 664 | } |
| 687 | 665 | } |
@@ -713,8 +691,7 @@ discard block |
||
| 713 | 691 | foreach ($this->recur["changed_occurrences"] as $entry) { |
| 714 | 692 | if (!$this->isSameDay($entry["basedate"], $base_date)) { |
| 715 | 693 | $new[] = $entry; |
| 716 | - } |
|
| 717 | - else { |
|
| 694 | + } else { |
|
| 718 | 695 | $this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60)); |
| 719 | 696 | } |
| 720 | 697 | } |
@@ -1083,8 +1060,7 @@ discard block |
||
| 1083 | 1060 | public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) { |
| 1084 | 1061 | if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) { |
| 1085 | 1062 | $this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips); |
| 1086 | - } |
|
| 1087 | - else { |
|
| 1063 | + } else { |
|
| 1088 | 1064 | $this->setAllExceptionRecipients($message, $exception_recips); |
| 1089 | 1065 | } |
| 1090 | 1066 | } |
@@ -1125,8 +1101,7 @@ discard block |
||
| 1125 | 1101 | foreach ($exception_recips['remove'] as &$recip) { |
| 1126 | 1102 | if (!isset($recip[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
| 1127 | 1103 | $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
| 1128 | - } |
|
| 1129 | - else { |
|
| 1104 | + } else { |
|
| 1130 | 1105 | $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
| 1131 | 1106 | } |
| 1132 | 1107 | $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone; // No Response required |
@@ -1201,8 +1176,7 @@ discard block |
||
| 1201 | 1176 | if (!$foundInDeletedRecipients) { |
| 1202 | 1177 | if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) { |
| 1203 | 1178 | $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted; |
| 1204 | - } |
|
| 1205 | - else { |
|
| 1179 | + } else { |
|
| 1206 | 1180 | $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable; |
| 1207 | 1181 | } |
| 1208 | 1182 | $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone; // No Response required |
@@ -1221,8 +1195,7 @@ discard block |
||
| 1221 | 1195 | } |
| 1222 | 1196 | } |
| 1223 | 1197 | $exception_recips = array_merge($exception_recips, $deletedRecipients); |
| 1224 | - } |
|
| 1225 | - else { |
|
| 1198 | + } else { |
|
| 1226 | 1199 | $exception_recips = $recipientRows; |
| 1227 | 1200 | } |
| 1228 | 1201 | |
@@ -1267,8 +1240,7 @@ discard block |
||
| 1267 | 1240 | foreach ($recipients as $key => $recipient) { |
| 1268 | 1241 | if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) { |
| 1269 | 1242 | $hasOrganizer = true; |
| 1270 | - } |
|
| 1271 | - elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 1243 | + } elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) { |
|
| 1272 | 1244 | // Recipients for an occurrence |
| 1273 | 1245 | $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse; |
| 1274 | 1246 | } |
@@ -6,7 +6,7 @@ |
||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | if (!function_exists("mapi_prop_tag")) { |
| 9 | - throw new FatalMisconfigurationException("PHP-MAPI extension is not available"); |
|
| 9 | + throw new FatalMisconfigurationException("PHP-MAPI extension is not available"); |
|
| 10 | 10 | } |
| 11 | 11 | |
| 12 | 12 | define('PR_ACKNOWLEDGEMENT_MODE', mapi_prop_tag(PT_LONG, 0x0001)); |
@@ -6,393 +6,393 @@ |
||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | class FreeBusyPublish { |
| 9 | - public $session; |
|
| 10 | - public $calendar; |
|
| 11 | - public $entryid; |
|
| 12 | - public $starttime; |
|
| 13 | - public $length; |
|
| 14 | - public $store; |
|
| 15 | - public $proptags; |
|
| 16 | - |
|
| 17 | - /** |
|
| 18 | - * Constructor. |
|
| 19 | - * |
|
| 20 | - * @param mapi_session $session MAPI Session |
|
| 21 | - * @param mapi_folder $calendar Calendar to publish |
|
| 22 | - * @param string $entryid AddressBook Entry ID for the user we're publishing for |
|
| 23 | - * @param mixed $store |
|
| 24 | - */ |
|
| 25 | - public function __construct($session, $store, $calendar, $entryid) { |
|
| 26 | - $properties["entryid"] = PR_ENTRYID; |
|
| 27 | - $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
|
| 28 | - $properties["message_class"] = PR_MESSAGE_CLASS; |
|
| 29 | - $properties["icon_index"] = PR_ICON_INDEX; |
|
| 30 | - $properties["subject"] = PR_SUBJECT; |
|
| 31 | - $properties["display_to"] = PR_DISPLAY_TO; |
|
| 32 | - $properties["importance"] = PR_IMPORTANCE; |
|
| 33 | - $properties["sensitivity"] = PR_SENSITIVITY; |
|
| 34 | - $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d"; |
|
| 35 | - $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e"; |
|
| 36 | - $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223"; |
|
| 37 | - $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216"; |
|
| 38 | - $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205"; |
|
| 39 | - $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214"; |
|
| 40 | - $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215"; |
|
| 41 | - $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; |
|
| 42 | - $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217"; |
|
| 43 | - $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235"; |
|
| 44 | - $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236"; |
|
| 45 | - $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208"; |
|
| 46 | - $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213"; |
|
| 47 | - $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218"; |
|
| 48 | - $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 49 | - $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 50 | - $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
|
| 51 | - $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
|
| 52 | - $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 53 | - $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502"; |
|
| 54 | - $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
|
| 55 | - $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
|
| 56 | - $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228"; |
|
| 57 | - $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233"; |
|
| 58 | - $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 59 | - |
|
| 60 | - $this->session = $session; |
|
| 61 | - $this->calendar = $calendar; |
|
| 62 | - $this->entryid = $entryid; |
|
| 63 | - $this->store = $store; |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - /** |
|
| 67 | - * Function is used to get the calendar data based on give date range. |
|
| 68 | - * |
|
| 69 | - * @param timestamp $starttime time from which to get the calendar data |
|
| 70 | - * @param timestamp $length time up till now get the calendar data |
|
| 71 | - * |
|
| 72 | - * @return array return the calendar data array |
|
| 73 | - */ |
|
| 74 | - public function getCalendarData($starttime, $length) { |
|
| 75 | - $start = $starttime; |
|
| 76 | - $end = $length; |
|
| 77 | - |
|
| 78 | - // Get all the items in the calendar that we need |
|
| 79 | - |
|
| 80 | - $calendaritems = []; |
|
| 81 | - |
|
| 82 | - $restrict = [ |
|
| 83 | - RES_OR, |
|
| 84 | - [ |
|
| 85 | - // OR |
|
| 86 | - // (item[start] >= start && item[start] <= end) |
|
| 87 | - [ |
|
| 88 | - RES_AND, |
|
| 89 | - [ |
|
| 90 | - [ |
|
| 91 | - RES_PROPERTY, |
|
| 92 | - [ |
|
| 93 | - RELOP => RELOP_GE, |
|
| 94 | - ULPROPTAG => $this->proptags["startdate"], |
|
| 95 | - VALUE => $start, |
|
| 96 | - ], |
|
| 97 | - ], |
|
| 98 | - [ |
|
| 99 | - RES_PROPERTY, |
|
| 100 | - [ |
|
| 101 | - RELOP => RELOP_LE, |
|
| 102 | - ULPROPTAG => $this->proptags["startdate"], |
|
| 103 | - VALUE => $end, |
|
| 104 | - ], |
|
| 105 | - ], |
|
| 106 | - ], |
|
| 107 | - ], |
|
| 108 | - // OR |
|
| 109 | - // (item[end] >= start && item[end] <= end) |
|
| 110 | - [ |
|
| 111 | - RES_AND, |
|
| 112 | - [ |
|
| 113 | - [ |
|
| 114 | - RES_PROPERTY, |
|
| 115 | - [ |
|
| 116 | - RELOP => RELOP_GE, |
|
| 117 | - ULPROPTAG => $this->proptags["duedate"], |
|
| 118 | - VALUE => $start, |
|
| 119 | - ], |
|
| 120 | - ], |
|
| 121 | - [ |
|
| 122 | - RES_PROPERTY, |
|
| 123 | - [ |
|
| 124 | - RELOP => RELOP_LE, |
|
| 125 | - ULPROPTAG => $this->proptags["duedate"], |
|
| 126 | - VALUE => $end, |
|
| 127 | - ], |
|
| 128 | - ], |
|
| 129 | - ], |
|
| 130 | - ], |
|
| 131 | - // OR |
|
| 132 | - // (item[start] < start && item[end] > end) |
|
| 133 | - [ |
|
| 134 | - RES_AND, |
|
| 135 | - [ |
|
| 136 | - [ |
|
| 137 | - RES_PROPERTY, |
|
| 138 | - [ |
|
| 139 | - RELOP => RELOP_LT, |
|
| 140 | - ULPROPTAG => $this->proptags["startdate"], |
|
| 141 | - VALUE => $start, |
|
| 142 | - ], |
|
| 143 | - ], |
|
| 144 | - [ |
|
| 145 | - RES_PROPERTY, |
|
| 146 | - [ |
|
| 147 | - RELOP => RELOP_GT, |
|
| 148 | - ULPROPTAG => $this->proptags["duedate"], |
|
| 149 | - VALUE => $end, |
|
| 150 | - ], |
|
| 151 | - ], |
|
| 152 | - ], |
|
| 153 | - ], |
|
| 154 | - // OR |
|
| 155 | - [ |
|
| 156 | - RES_OR, |
|
| 157 | - [ |
|
| 158 | - // OR |
|
| 159 | - // (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start) |
|
| 160 | - [ |
|
| 161 | - RES_AND, |
|
| 162 | - [ |
|
| 163 | - [ |
|
| 164 | - RES_EXIST, |
|
| 165 | - [ULPROPTAG => $this->proptags["enddate_recurring"]], |
|
| 166 | - ], |
|
| 167 | - [ |
|
| 168 | - RES_PROPERTY, |
|
| 169 | - [ |
|
| 170 | - RELOP => RELOP_EQ, |
|
| 171 | - ULPROPTAG => $this->proptags["recurring"], |
|
| 172 | - VALUE => true, |
|
| 173 | - ], |
|
| 174 | - ], |
|
| 175 | - [ |
|
| 176 | - RES_PROPERTY, |
|
| 177 | - [ |
|
| 178 | - RELOP => RELOP_GE, |
|
| 179 | - ULPROPTAG => $this->proptags["enddate_recurring"], |
|
| 180 | - VALUE => $start, |
|
| 181 | - ], |
|
| 182 | - ], |
|
| 183 | - ], |
|
| 184 | - ], |
|
| 185 | - // OR |
|
| 186 | - // (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end) |
|
| 187 | - [ |
|
| 188 | - RES_AND, |
|
| 189 | - [ |
|
| 190 | - [ |
|
| 191 | - RES_NOT, |
|
| 192 | - [ |
|
| 193 | - [ |
|
| 194 | - RES_EXIST, |
|
| 195 | - [ULPROPTAG => $this->proptags["enddate_recurring"], |
|
| 196 | - ], |
|
| 197 | - ], |
|
| 198 | - ], |
|
| 199 | - ], |
|
| 200 | - [ |
|
| 201 | - RES_PROPERTY, |
|
| 202 | - [ |
|
| 203 | - RELOP => RELOP_LE, |
|
| 204 | - ULPROPTAG => $this->proptags["startdate"], |
|
| 205 | - VALUE => $end, |
|
| 206 | - ], |
|
| 207 | - ], |
|
| 208 | - [ |
|
| 209 | - RES_PROPERTY, |
|
| 210 | - [ |
|
| 211 | - RELOP => RELOP_EQ, |
|
| 212 | - ULPROPTAG => $this->proptags["recurring"], |
|
| 213 | - VALUE => true, |
|
| 214 | - ], |
|
| 215 | - ], |
|
| 216 | - ], |
|
| 217 | - ], |
|
| 218 | - ], |
|
| 219 | - ], // EXISTS OR |
|
| 220 | - ], |
|
| 221 | - ]; // global OR |
|
| 222 | - |
|
| 223 | - $contents = mapi_folder_getcontentstable($this->calendar); |
|
| 224 | - mapi_table_restrict($contents, $restrict); |
|
| 225 | - |
|
| 226 | - while (1) { |
|
| 227 | - $rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50); |
|
| 228 | - |
|
| 229 | - if (!is_array($rows)) { |
|
| 230 | - break; |
|
| 231 | - } |
|
| 232 | - |
|
| 233 | - if (empty($rows)) { |
|
| 234 | - break; |
|
| 235 | - } |
|
| 236 | - |
|
| 237 | - foreach ($rows as $row) { |
|
| 238 | - $occurrences = []; |
|
| 239 | - if (isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) { |
|
| 240 | - $recur = new Recurrence($this->store, $row); |
|
| 241 | - |
|
| 242 | - $occurrences = $recur->getItems($starttime, $length); |
|
| 243 | - } |
|
| 244 | - else { |
|
| 245 | - $occurrences[] = $row; |
|
| 246 | - } |
|
| 247 | - |
|
| 248 | - $calendaritems = array_merge($calendaritems, $occurrences); |
|
| 249 | - } |
|
| 250 | - } |
|
| 251 | - |
|
| 252 | - // $calendaritems now contains all the calendar items in the specified time |
|
| 253 | - // frame. We now need to merge these into a flat array of begin/end/status |
|
| 254 | - // objects. This also filters out all the 'free' items (status 0) |
|
| 255 | - return $this->mergeItemsFB($calendaritems); |
|
| 256 | - } |
|
| 257 | - |
|
| 258 | - /** |
|
| 259 | - * Publishes Free/Busy information of user. |
|
| 260 | - * |
|
| 261 | - * @param timestamp $starttime Time from which to publish data (usually now) |
|
| 262 | - * @param timestamp $length Time of seconds from $starttime we should publish |
|
| 263 | - * @param mixed $start |
|
| 264 | - * @param mixed $end |
|
| 265 | - */ |
|
| 266 | - public function publishFB($start, $end) { |
|
| 267 | - $freebusy = $this->getCalendarData($start, $end); |
|
| 268 | - |
|
| 269 | - // Get the FB interface |
|
| 270 | - try { |
|
| 271 | - $fbsupport = mapi_freebusysupport_open($this->session, $this->store); |
|
| 272 | - } |
|
| 273 | - catch (MAPIException $e) { |
|
| 274 | - if ($e->getCode() == MAPI_E_NOT_FOUND) { |
|
| 275 | - $e->setHandled(); |
|
| 276 | - SLog::Write(LOGLEVEL_WARN, "Error in opening freebusysupport object."); |
|
| 277 | - } |
|
| 278 | - } |
|
| 279 | - |
|
| 280 | - // Open updater for this user |
|
| 281 | - if (isset($fbsupport) && $fbsupport) { |
|
| 282 | - $updaters = mapi_freebusysupport_loadupdate($fbsupport, [$this->entryid]); |
|
| 283 | - |
|
| 284 | - $updater = $updaters[0]; |
|
| 285 | - |
|
| 286 | - // Send the data |
|
| 287 | - mapi_freebusyupdate_reset($updater); |
|
| 288 | - mapi_freebusyupdate_publish($updater, $freebusy); |
|
| 289 | - mapi_freebusyupdate_savechanges($updater, $start - 24 * 60 * 60, $end); |
|
| 290 | - |
|
| 291 | - // We're finished |
|
| 292 | - mapi_freebusysupport_close($fbsupport); |
|
| 293 | - } |
|
| 294 | - else { |
|
| 295 | - SLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available"); |
|
| 296 | - } |
|
| 297 | - } |
|
| 298 | - |
|
| 299 | - /** |
|
| 300 | - * Sorts by timestamp, if equal, then end before start. |
|
| 301 | - * |
|
| 302 | - * @param mixed $a |
|
| 303 | - * @param mixed $b |
|
| 304 | - */ |
|
| 305 | - public function cmp($a, $b) { |
|
| 306 | - if ($a["time"] == $b["time"]) { |
|
| 307 | - if ($a["type"] < $b["type"]) { |
|
| 308 | - return 1; |
|
| 309 | - } |
|
| 310 | - if ($a["type"] > $b["type"]) { |
|
| 311 | - return -1; |
|
| 312 | - } |
|
| 313 | - |
|
| 314 | - return 0; |
|
| 315 | - } |
|
| 316 | - |
|
| 317 | - return $a["time"] > $b["time"] ? 1 : -1; |
|
| 318 | - } |
|
| 319 | - |
|
| 320 | - /** |
|
| 321 | - * Function mergeItems. |
|
| 322 | - * |
|
| 323 | - * @author Steve Hardy |
|
| 324 | - * |
|
| 325 | - * @param mixed $items |
|
| 326 | - */ |
|
| 327 | - public function mergeItemsFB($items) { |
|
| 328 | - $merged = []; |
|
| 329 | - $timestamps = []; |
|
| 330 | - $csubj = []; |
|
| 331 | - $cbusy = []; |
|
| 332 | - $level = 0; |
|
| 333 | - $laststart = null; |
|
| 334 | - |
|
| 335 | - foreach ($items as $item) { |
|
| 336 | - $ts["type"] = 0; |
|
| 337 | - $ts["time"] = $item[$this->proptags["startdate"]]; |
|
| 338 | - $ts["subject"] = $item[PR_SUBJECT]; |
|
| 339 | - $ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197 |
|
| 340 | - $timestamps[] = $ts; |
|
| 341 | - |
|
| 342 | - $ts["type"] = 1; |
|
| 343 | - $ts["time"] = $item[$this->proptags["duedate"]]; |
|
| 344 | - $ts["subject"] = $item[PR_SUBJECT]; |
|
| 345 | - $ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197 |
|
| 346 | - $timestamps[] = $ts; |
|
| 347 | - } |
|
| 348 | - |
|
| 349 | - usort($timestamps, [$this, "cmp"]); |
|
| 350 | - $laststart = 0; // seb added |
|
| 351 | - |
|
| 352 | - foreach ($timestamps as $ts) { |
|
| 353 | - switch ($ts["type"]) { |
|
| 354 | - case 0: // Start |
|
| 355 | - if ($level != 0 && $laststart != $ts["time"]) { |
|
| 356 | - $newitem["start"] = $laststart; |
|
| 357 | - $newitem["end"] = $ts["time"]; |
|
| 358 | - $newitem["subject"] = join(",", $csubj); |
|
| 359 | - $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0; |
|
| 360 | - if ($newitem["status"] > 0) { |
|
| 361 | - $merged[] = $newitem; |
|
| 362 | - } |
|
| 363 | - } |
|
| 364 | - |
|
| 365 | - ++$level; |
|
| 366 | - |
|
| 367 | - $csubj[] = $ts["subject"]; |
|
| 368 | - $cbusy[] = $ts["status"]; |
|
| 369 | - |
|
| 370 | - $laststart = $ts["time"]; |
|
| 371 | - |
|
| 372 | - break; |
|
| 373 | - |
|
| 374 | - case 1: // End |
|
| 375 | - if ($laststart != $ts["time"]) { |
|
| 376 | - $newitem["start"] = $laststart; |
|
| 377 | - $newitem["end"] = $ts["time"]; |
|
| 378 | - $newitem["subject"] = join(",", $csubj); |
|
| 379 | - $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0; |
|
| 380 | - if ($newitem["status"] > 0) { |
|
| 381 | - $merged[] = $newitem; |
|
| 382 | - } |
|
| 383 | - } |
|
| 384 | - |
|
| 385 | - --$level; |
|
| 386 | - |
|
| 387 | - array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1); |
|
| 388 | - array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1); |
|
| 389 | - |
|
| 390 | - $laststart = $ts["time"]; |
|
| 391 | - |
|
| 392 | - break; |
|
| 393 | - } |
|
| 394 | - } |
|
| 395 | - |
|
| 396 | - return $merged; |
|
| 397 | - } |
|
| 9 | + public $session; |
|
| 10 | + public $calendar; |
|
| 11 | + public $entryid; |
|
| 12 | + public $starttime; |
|
| 13 | + public $length; |
|
| 14 | + public $store; |
|
| 15 | + public $proptags; |
|
| 16 | + |
|
| 17 | + /** |
|
| 18 | + * Constructor. |
|
| 19 | + * |
|
| 20 | + * @param mapi_session $session MAPI Session |
|
| 21 | + * @param mapi_folder $calendar Calendar to publish |
|
| 22 | + * @param string $entryid AddressBook Entry ID for the user we're publishing for |
|
| 23 | + * @param mixed $store |
|
| 24 | + */ |
|
| 25 | + public function __construct($session, $store, $calendar, $entryid) { |
|
| 26 | + $properties["entryid"] = PR_ENTRYID; |
|
| 27 | + $properties["parent_entryid"] = PR_PARENT_ENTRYID; |
|
| 28 | + $properties["message_class"] = PR_MESSAGE_CLASS; |
|
| 29 | + $properties["icon_index"] = PR_ICON_INDEX; |
|
| 30 | + $properties["subject"] = PR_SUBJECT; |
|
| 31 | + $properties["display_to"] = PR_DISPLAY_TO; |
|
| 32 | + $properties["importance"] = PR_IMPORTANCE; |
|
| 33 | + $properties["sensitivity"] = PR_SENSITIVITY; |
|
| 34 | + $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d"; |
|
| 35 | + $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e"; |
|
| 36 | + $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223"; |
|
| 37 | + $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216"; |
|
| 38 | + $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205"; |
|
| 39 | + $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214"; |
|
| 40 | + $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215"; |
|
| 41 | + $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506"; |
|
| 42 | + $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217"; |
|
| 43 | + $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235"; |
|
| 44 | + $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236"; |
|
| 45 | + $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208"; |
|
| 46 | + $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213"; |
|
| 47 | + $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218"; |
|
| 48 | + $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503"; |
|
| 49 | + $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501"; |
|
| 50 | + $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a"; |
|
| 51 | + $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586"; |
|
| 52 | + $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; |
|
| 53 | + $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502"; |
|
| 54 | + $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516"; |
|
| 55 | + $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517"; |
|
| 56 | + $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228"; |
|
| 57 | + $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233"; |
|
| 58 | + $this->proptags = getPropIdsFromStrings($store, $properties); |
|
| 59 | + |
|
| 60 | + $this->session = $session; |
|
| 61 | + $this->calendar = $calendar; |
|
| 62 | + $this->entryid = $entryid; |
|
| 63 | + $this->store = $store; |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + /** |
|
| 67 | + * Function is used to get the calendar data based on give date range. |
|
| 68 | + * |
|
| 69 | + * @param timestamp $starttime time from which to get the calendar data |
|
| 70 | + * @param timestamp $length time up till now get the calendar data |
|
| 71 | + * |
|
| 72 | + * @return array return the calendar data array |
|
| 73 | + */ |
|
| 74 | + public function getCalendarData($starttime, $length) { |
|
| 75 | + $start = $starttime; |
|
| 76 | + $end = $length; |
|
| 77 | + |
|
| 78 | + // Get all the items in the calendar that we need |
|
| 79 | + |
|
| 80 | + $calendaritems = []; |
|
| 81 | + |
|
| 82 | + $restrict = [ |
|
| 83 | + RES_OR, |
|
| 84 | + [ |
|
| 85 | + // OR |
|
| 86 | + // (item[start] >= start && item[start] <= end) |
|
| 87 | + [ |
|
| 88 | + RES_AND, |
|
| 89 | + [ |
|
| 90 | + [ |
|
| 91 | + RES_PROPERTY, |
|
| 92 | + [ |
|
| 93 | + RELOP => RELOP_GE, |
|
| 94 | + ULPROPTAG => $this->proptags["startdate"], |
|
| 95 | + VALUE => $start, |
|
| 96 | + ], |
|
| 97 | + ], |
|
| 98 | + [ |
|
| 99 | + RES_PROPERTY, |
|
| 100 | + [ |
|
| 101 | + RELOP => RELOP_LE, |
|
| 102 | + ULPROPTAG => $this->proptags["startdate"], |
|
| 103 | + VALUE => $end, |
|
| 104 | + ], |
|
| 105 | + ], |
|
| 106 | + ], |
|
| 107 | + ], |
|
| 108 | + // OR |
|
| 109 | + // (item[end] >= start && item[end] <= end) |
|
| 110 | + [ |
|
| 111 | + RES_AND, |
|
| 112 | + [ |
|
| 113 | + [ |
|
| 114 | + RES_PROPERTY, |
|
| 115 | + [ |
|
| 116 | + RELOP => RELOP_GE, |
|
| 117 | + ULPROPTAG => $this->proptags["duedate"], |
|
| 118 | + VALUE => $start, |
|
| 119 | + ], |
|
| 120 | + ], |
|
| 121 | + [ |
|
| 122 | + RES_PROPERTY, |
|
| 123 | + [ |
|
| 124 | + RELOP => RELOP_LE, |
|
| 125 | + ULPROPTAG => $this->proptags["duedate"], |
|
| 126 | + VALUE => $end, |
|
| 127 | + ], |
|
| 128 | + ], |
|
| 129 | + ], |
|
| 130 | + ], |
|
| 131 | + // OR |
|
| 132 | + // (item[start] < start && item[end] > end) |
|
| 133 | + [ |
|
| 134 | + RES_AND, |
|
| 135 | + [ |
|
| 136 | + [ |
|
| 137 | + RES_PROPERTY, |
|
| 138 | + [ |
|
| 139 | + RELOP => RELOP_LT, |
|
| 140 | + ULPROPTAG => $this->proptags["startdate"], |
|
| 141 | + VALUE => $start, |
|
| 142 | + ], |
|
| 143 | + ], |
|
| 144 | + [ |
|
| 145 | + RES_PROPERTY, |
|
| 146 | + [ |
|
| 147 | + RELOP => RELOP_GT, |
|
| 148 | + ULPROPTAG => $this->proptags["duedate"], |
|
| 149 | + VALUE => $end, |
|
| 150 | + ], |
|
| 151 | + ], |
|
| 152 | + ], |
|
| 153 | + ], |
|
| 154 | + // OR |
|
| 155 | + [ |
|
| 156 | + RES_OR, |
|
| 157 | + [ |
|
| 158 | + // OR |
|
| 159 | + // (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start) |
|
| 160 | + [ |
|
| 161 | + RES_AND, |
|
| 162 | + [ |
|
| 163 | + [ |
|
| 164 | + RES_EXIST, |
|
| 165 | + [ULPROPTAG => $this->proptags["enddate_recurring"]], |
|
| 166 | + ], |
|
| 167 | + [ |
|
| 168 | + RES_PROPERTY, |
|
| 169 | + [ |
|
| 170 | + RELOP => RELOP_EQ, |
|
| 171 | + ULPROPTAG => $this->proptags["recurring"], |
|
| 172 | + VALUE => true, |
|
| 173 | + ], |
|
| 174 | + ], |
|
| 175 | + [ |
|
| 176 | + RES_PROPERTY, |
|
| 177 | + [ |
|
| 178 | + RELOP => RELOP_GE, |
|
| 179 | + ULPROPTAG => $this->proptags["enddate_recurring"], |
|
| 180 | + VALUE => $start, |
|
| 181 | + ], |
|
| 182 | + ], |
|
| 183 | + ], |
|
| 184 | + ], |
|
| 185 | + // OR |
|
| 186 | + // (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end) |
|
| 187 | + [ |
|
| 188 | + RES_AND, |
|
| 189 | + [ |
|
| 190 | + [ |
|
| 191 | + RES_NOT, |
|
| 192 | + [ |
|
| 193 | + [ |
|
| 194 | + RES_EXIST, |
|
| 195 | + [ULPROPTAG => $this->proptags["enddate_recurring"], |
|
| 196 | + ], |
|
| 197 | + ], |
|
| 198 | + ], |
|
| 199 | + ], |
|
| 200 | + [ |
|
| 201 | + RES_PROPERTY, |
|
| 202 | + [ |
|
| 203 | + RELOP => RELOP_LE, |
|
| 204 | + ULPROPTAG => $this->proptags["startdate"], |
|
| 205 | + VALUE => $end, |
|
| 206 | + ], |
|
| 207 | + ], |
|
| 208 | + [ |
|
| 209 | + RES_PROPERTY, |
|
| 210 | + [ |
|
| 211 | + RELOP => RELOP_EQ, |
|
| 212 | + ULPROPTAG => $this->proptags["recurring"], |
|
| 213 | + VALUE => true, |
|
| 214 | + ], |
|
| 215 | + ], |
|
| 216 | + ], |
|
| 217 | + ], |
|
| 218 | + ], |
|
| 219 | + ], // EXISTS OR |
|
| 220 | + ], |
|
| 221 | + ]; // global OR |
|
| 222 | + |
|
| 223 | + $contents = mapi_folder_getcontentstable($this->calendar); |
|
| 224 | + mapi_table_restrict($contents, $restrict); |
|
| 225 | + |
|
| 226 | + while (1) { |
|
| 227 | + $rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50); |
|
| 228 | + |
|
| 229 | + if (!is_array($rows)) { |
|
| 230 | + break; |
|
| 231 | + } |
|
| 232 | + |
|
| 233 | + if (empty($rows)) { |
|
| 234 | + break; |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + foreach ($rows as $row) { |
|
| 238 | + $occurrences = []; |
|
| 239 | + if (isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) { |
|
| 240 | + $recur = new Recurrence($this->store, $row); |
|
| 241 | + |
|
| 242 | + $occurrences = $recur->getItems($starttime, $length); |
|
| 243 | + } |
|
| 244 | + else { |
|
| 245 | + $occurrences[] = $row; |
|
| 246 | + } |
|
| 247 | + |
|
| 248 | + $calendaritems = array_merge($calendaritems, $occurrences); |
|
| 249 | + } |
|
| 250 | + } |
|
| 251 | + |
|
| 252 | + // $calendaritems now contains all the calendar items in the specified time |
|
| 253 | + // frame. We now need to merge these into a flat array of begin/end/status |
|
| 254 | + // objects. This also filters out all the 'free' items (status 0) |
|
| 255 | + return $this->mergeItemsFB($calendaritems); |
|
| 256 | + } |
|
| 257 | + |
|
| 258 | + /** |
|
| 259 | + * Publishes Free/Busy information of user. |
|
| 260 | + * |
|
| 261 | + * @param timestamp $starttime Time from which to publish data (usually now) |
|
| 262 | + * @param timestamp $length Time of seconds from $starttime we should publish |
|
| 263 | + * @param mixed $start |
|
| 264 | + * @param mixed $end |
|
| 265 | + */ |
|
| 266 | + public function publishFB($start, $end) { |
|
| 267 | + $freebusy = $this->getCalendarData($start, $end); |
|
| 268 | + |
|
| 269 | + // Get the FB interface |
|
| 270 | + try { |
|
| 271 | + $fbsupport = mapi_freebusysupport_open($this->session, $this->store); |
|
| 272 | + } |
|
| 273 | + catch (MAPIException $e) { |
|
| 274 | + if ($e->getCode() == MAPI_E_NOT_FOUND) { |
|
| 275 | + $e->setHandled(); |
|
| 276 | + SLog::Write(LOGLEVEL_WARN, "Error in opening freebusysupport object."); |
|
| 277 | + } |
|
| 278 | + } |
|
| 279 | + |
|
| 280 | + // Open updater for this user |
|
| 281 | + if (isset($fbsupport) && $fbsupport) { |
|
| 282 | + $updaters = mapi_freebusysupport_loadupdate($fbsupport, [$this->entryid]); |
|
| 283 | + |
|
| 284 | + $updater = $updaters[0]; |
|
| 285 | + |
|
| 286 | + // Send the data |
|
| 287 | + mapi_freebusyupdate_reset($updater); |
|
| 288 | + mapi_freebusyupdate_publish($updater, $freebusy); |
|
| 289 | + mapi_freebusyupdate_savechanges($updater, $start - 24 * 60 * 60, $end); |
|
| 290 | + |
|
| 291 | + // We're finished |
|
| 292 | + mapi_freebusysupport_close($fbsupport); |
|
| 293 | + } |
|
| 294 | + else { |
|
| 295 | + SLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available"); |
|
| 296 | + } |
|
| 297 | + } |
|
| 298 | + |
|
| 299 | + /** |
|
| 300 | + * Sorts by timestamp, if equal, then end before start. |
|
| 301 | + * |
|
| 302 | + * @param mixed $a |
|
| 303 | + * @param mixed $b |
|
| 304 | + */ |
|
| 305 | + public function cmp($a, $b) { |
|
| 306 | + if ($a["time"] == $b["time"]) { |
|
| 307 | + if ($a["type"] < $b["type"]) { |
|
| 308 | + return 1; |
|
| 309 | + } |
|
| 310 | + if ($a["type"] > $b["type"]) { |
|
| 311 | + return -1; |
|
| 312 | + } |
|
| 313 | + |
|
| 314 | + return 0; |
|
| 315 | + } |
|
| 316 | + |
|
| 317 | + return $a["time"] > $b["time"] ? 1 : -1; |
|
| 318 | + } |
|
| 319 | + |
|
| 320 | + /** |
|
| 321 | + * Function mergeItems. |
|
| 322 | + * |
|
| 323 | + * @author Steve Hardy |
|
| 324 | + * |
|
| 325 | + * @param mixed $items |
|
| 326 | + */ |
|
| 327 | + public function mergeItemsFB($items) { |
|
| 328 | + $merged = []; |
|
| 329 | + $timestamps = []; |
|
| 330 | + $csubj = []; |
|
| 331 | + $cbusy = []; |
|
| 332 | + $level = 0; |
|
| 333 | + $laststart = null; |
|
| 334 | + |
|
| 335 | + foreach ($items as $item) { |
|
| 336 | + $ts["type"] = 0; |
|
| 337 | + $ts["time"] = $item[$this->proptags["startdate"]]; |
|
| 338 | + $ts["subject"] = $item[PR_SUBJECT]; |
|
| 339 | + $ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197 |
|
| 340 | + $timestamps[] = $ts; |
|
| 341 | + |
|
| 342 | + $ts["type"] = 1; |
|
| 343 | + $ts["time"] = $item[$this->proptags["duedate"]]; |
|
| 344 | + $ts["subject"] = $item[PR_SUBJECT]; |
|
| 345 | + $ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : fbFree; // ZP-197 |
|
| 346 | + $timestamps[] = $ts; |
|
| 347 | + } |
|
| 348 | + |
|
| 349 | + usort($timestamps, [$this, "cmp"]); |
|
| 350 | + $laststart = 0; // seb added |
|
| 351 | + |
|
| 352 | + foreach ($timestamps as $ts) { |
|
| 353 | + switch ($ts["type"]) { |
|
| 354 | + case 0: // Start |
|
| 355 | + if ($level != 0 && $laststart != $ts["time"]) { |
|
| 356 | + $newitem["start"] = $laststart; |
|
| 357 | + $newitem["end"] = $ts["time"]; |
|
| 358 | + $newitem["subject"] = join(",", $csubj); |
|
| 359 | + $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0; |
|
| 360 | + if ($newitem["status"] > 0) { |
|
| 361 | + $merged[] = $newitem; |
|
| 362 | + } |
|
| 363 | + } |
|
| 364 | + |
|
| 365 | + ++$level; |
|
| 366 | + |
|
| 367 | + $csubj[] = $ts["subject"]; |
|
| 368 | + $cbusy[] = $ts["status"]; |
|
| 369 | + |
|
| 370 | + $laststart = $ts["time"]; |
|
| 371 | + |
|
| 372 | + break; |
|
| 373 | + |
|
| 374 | + case 1: // End |
|
| 375 | + if ($laststart != $ts["time"]) { |
|
| 376 | + $newitem["start"] = $laststart; |
|
| 377 | + $newitem["end"] = $ts["time"]; |
|
| 378 | + $newitem["subject"] = join(",", $csubj); |
|
| 379 | + $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0; |
|
| 380 | + if ($newitem["status"] > 0) { |
|
| 381 | + $merged[] = $newitem; |
|
| 382 | + } |
|
| 383 | + } |
|
| 384 | + |
|
| 385 | + --$level; |
|
| 386 | + |
|
| 387 | + array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1); |
|
| 388 | + array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1); |
|
| 389 | + |
|
| 390 | + $laststart = $ts["time"]; |
|
| 391 | + |
|
| 392 | + break; |
|
| 393 | + } |
|
| 394 | + } |
|
| 395 | + |
|
| 396 | + return $merged; |
|
| 397 | + } |
|
| 398 | 398 | } |
@@ -218,7 +218,7 @@ |
||
| 218 | 218 | ], |
| 219 | 219 | ], // EXISTS OR |
| 220 | 220 | ], |
| 221 | - ]; // global OR |
|
| 221 | + ]; // global OR |
|
| 222 | 222 | |
| 223 | 223 | $contents = mapi_folder_getcontentstable($this->calendar); |
| 224 | 224 | mapi_table_restrict($contents, $restrict); |
@@ -240,8 +240,7 @@ discard block |
||
| 240 | 240 | $recur = new Recurrence($this->store, $row); |
| 241 | 241 | |
| 242 | 242 | $occurrences = $recur->getItems($starttime, $length); |
| 243 | - } |
|
| 244 | - else { |
|
| 243 | + } else { |
|
| 245 | 244 | $occurrences[] = $row; |
| 246 | 245 | } |
| 247 | 246 | |
@@ -269,8 +268,7 @@ discard block |
||
| 269 | 268 | // Get the FB interface |
| 270 | 269 | try { |
| 271 | 270 | $fbsupport = mapi_freebusysupport_open($this->session, $this->store); |
| 272 | - } |
|
| 273 | - catch (MAPIException $e) { |
|
| 271 | + } catch (MAPIException $e) { |
|
| 274 | 272 | if ($e->getCode() == MAPI_E_NOT_FOUND) { |
| 275 | 273 | $e->setHandled(); |
| 276 | 274 | SLog::Write(LOGLEVEL_WARN, "Error in opening freebusysupport object."); |
@@ -290,8 +288,7 @@ discard block |
||
| 290 | 288 | |
| 291 | 289 | // We're finished |
| 292 | 290 | mapi_freebusysupport_close($fbsupport); |
| 293 | - } |
|
| 294 | - else { |
|
| 291 | + } else { |
|
| 295 | 292 | SLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available"); |
| 296 | 293 | } |
| 297 | 294 | } |
@@ -5,1983 +5,1983 @@ |
||
| 5 | 5 | * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | - /** |
|
| 9 | - * BaseRecurrence |
|
| 10 | - * this class is superclass for recurrence for appointments and tasks. This class provides all |
|
| 11 | - * basic features of recurrence. |
|
| 12 | - */ |
|
| 13 | - class BaseRecurrence { |
|
| 14 | - /** |
|
| 15 | - * @var object Mapi Message Store (may be null if readonly) |
|
| 16 | - */ |
|
| 17 | - public $store; |
|
| 18 | - |
|
| 19 | - /** |
|
| 20 | - * @var object Mapi Message (may be null if readonly) |
|
| 21 | - */ |
|
| 22 | - public $message; |
|
| 23 | - |
|
| 24 | - /** |
|
| 25 | - * @var array Message Properties |
|
| 26 | - */ |
|
| 27 | - public $messageprops; |
|
| 28 | - |
|
| 29 | - /** |
|
| 30 | - * @var array list of property tags |
|
| 31 | - */ |
|
| 32 | - public $proptags; |
|
| 33 | - |
|
| 34 | - /** |
|
| 35 | - * @var recurrence data of this calendar item |
|
| 36 | - */ |
|
| 37 | - public $recur; |
|
| 38 | - |
|
| 39 | - /** |
|
| 40 | - * @var Timezone data of this calendar item |
|
| 41 | - */ |
|
| 42 | - public $tz; |
|
| 43 | - |
|
| 44 | - /** |
|
| 45 | - * Constructor. |
|
| 46 | - * |
|
| 47 | - * @param resource $store MAPI Message Store Object |
|
| 48 | - * @param resource $message the MAPI (appointment) message |
|
| 49 | - * @param array $properties the list of MAPI properties the message has |
|
| 50 | - */ |
|
| 51 | - public function __construct($store, $message) { |
|
| 52 | - $this->store = $store; |
|
| 53 | - |
|
| 54 | - if (is_array($message)) { |
|
| 55 | - $this->messageprops = $message; |
|
| 56 | - } |
|
| 57 | - else { |
|
| 58 | - $this->message = $message; |
|
| 59 | - $this->messageprops = mapi_getprops($this->message, $this->proptags); |
|
| 60 | - } |
|
| 61 | - |
|
| 62 | - if (isset($this->messageprops[$this->proptags["recurring_data"]])) { |
|
| 63 | - // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface |
|
| 64 | - if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) { |
|
| 65 | - $this->getFullRecurrenceBlob(); |
|
| 66 | - } |
|
| 67 | - |
|
| 68 | - $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]); |
|
| 69 | - } |
|
| 70 | - if (isset($this->proptags["timezone_data"], $this->messageprops[$this->proptags["timezone_data"]])) { |
|
| 71 | - $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]); |
|
| 72 | - } |
|
| 73 | - } |
|
| 74 | - |
|
| 75 | - public function getRecurrence() { |
|
| 76 | - return $this->recur; |
|
| 77 | - } |
|
| 78 | - |
|
| 79 | - public function getFullRecurrenceBlob() { |
|
| 80 | - $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]); |
|
| 81 | - |
|
| 82 | - $recurrBlob = ''; |
|
| 83 | - $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0); |
|
| 84 | - $stat = mapi_stream_stat($stream); |
|
| 85 | - |
|
| 86 | - for ($i = 0; $i < $stat['cb']; $i += 1024) { |
|
| 87 | - $recurrBlob .= mapi_stream_read($stream, 1024); |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - if (!empty($recurrBlob)) { |
|
| 91 | - $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob; |
|
| 92 | - } |
|
| 93 | - } |
|
| 94 | - |
|
| 95 | - /** |
|
| 96 | - * Function for parsing the Recurrence value of a Calendar item. |
|
| 97 | - * |
|
| 98 | - * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the |
|
| 99 | - * data to this function |
|
| 100 | - * |
|
| 101 | - * Returns a structure containing the data: |
|
| 102 | - * |
|
| 103 | - * type - type of recurrence: day=10, week=11, month=12, year=13 |
|
| 104 | - * subtype - type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday) |
|
| 105 | - * start - unix timestamp of first occurrence |
|
| 106 | - * end - unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1 |
|
| 107 | - * numoccur - occurrences (may be very large when there is no end data) |
|
| 108 | - * |
|
| 109 | - * then, for each type: |
|
| 110 | - * |
|
| 111 | - * Daily: |
|
| 112 | - * everyn - every [everyn] days in minutes |
|
| 113 | - * regen - regenerating event (like tasks) |
|
| 114 | - * |
|
| 115 | - * Weekly: |
|
| 116 | - * everyn - every [everyn] weeks in weeks |
|
| 117 | - * regen - regenerating event (like tasks) |
|
| 118 | - * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
|
| 119 | - * |
|
| 120 | - * Monthly: |
|
| 121 | - * everyn - every [everyn] months |
|
| 122 | - * regen - regenerating event (like tasks) |
|
| 123 | - * |
|
| 124 | - * subtype 2: |
|
| 125 | - * monthday - on day [monthday] of the month |
|
| 126 | - * |
|
| 127 | - * subtype 3: |
|
| 128 | - * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
|
| 129 | - * nday - on [nday]'th [weekdays] of the month |
|
| 130 | - * |
|
| 131 | - * Yearly: |
|
| 132 | - * everyn - every [everyn] months (12, 24, 36, ...) |
|
| 133 | - * month - in month [month] (although the month is encoded in minutes since the startning of the year ........) |
|
| 134 | - * regen - regenerating event (like tasks) |
|
| 135 | - * |
|
| 136 | - * subtype 2: |
|
| 137 | - * monthday - on day [monthday] of the month |
|
| 138 | - * |
|
| 139 | - * subtype 3: |
|
| 140 | - * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
|
| 141 | - * nday - on [nday]'th [weekdays] of the month [month] |
|
| 142 | - * |
|
| 143 | - * @param string $rdata Binary string |
|
| 144 | - * |
|
| 145 | - * @return array recurrence data |
|
| 146 | - */ |
|
| 147 | - public function parseRecurrence($rdata) { |
|
| 148 | - if (strlen($rdata) < 10) { |
|
| 149 | - return; |
|
| 150 | - } |
|
| 151 | - |
|
| 152 | - $ret["changed_occurrences"] = []; |
|
| 153 | - $ret["deleted_occurrences"] = []; |
|
| 154 | - |
|
| 155 | - $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata); |
|
| 156 | - |
|
| 157 | - $ret["type"] = $data["rtype"]; |
|
| 158 | - $ret["subtype"] = $data["rtype2"]; |
|
| 159 | - $rdata = substr($rdata, 10); |
|
| 160 | - |
|
| 161 | - switch ($data["rtype"]) { |
|
| 162 | - case 0x0A: |
|
| 163 | - // Daily |
|
| 164 | - if (strlen($rdata) < 12) { |
|
| 165 | - return $ret; |
|
| 166 | - } |
|
| 167 | - |
|
| 168 | - $data = unpack("Vunknown/Veveryn/Vregen", $rdata); |
|
| 169 | - $ret["everyn"] = $data["everyn"]; |
|
| 170 | - $ret["regen"] = $data["regen"]; |
|
| 171 | - |
|
| 172 | - switch ($ret["subtype"]) { |
|
| 173 | - case 0: |
|
| 174 | - $rdata = substr($rdata, 12); |
|
| 175 | - |
|
| 176 | - break; |
|
| 177 | - |
|
| 178 | - case 1: |
|
| 179 | - $rdata = substr($rdata, 16); |
|
| 180 | - |
|
| 181 | - break; |
|
| 182 | - } |
|
| 183 | - |
|
| 184 | - break; |
|
| 185 | - |
|
| 186 | - case 0x0B: |
|
| 187 | - // Weekly |
|
| 188 | - if (strlen($rdata) < 16) { |
|
| 189 | - return $ret; |
|
| 190 | - } |
|
| 191 | - |
|
| 192 | - $data = unpack("Vconst1/Veveryn/Vregen", $rdata); |
|
| 193 | - $rdata = substr($rdata, 12); |
|
| 194 | - |
|
| 195 | - $ret["everyn"] = $data["everyn"]; |
|
| 196 | - $ret["regen"] = $data["regen"]; |
|
| 197 | - $ret["weekdays"] = 0; |
|
| 198 | - |
|
| 199 | - if ($data["regen"] == 0) { |
|
| 200 | - $data = unpack("Vweekdays", $rdata); |
|
| 201 | - $rdata = substr($rdata, 4); |
|
| 202 | - |
|
| 203 | - $ret["weekdays"] = $data["weekdays"]; |
|
| 204 | - } |
|
| 205 | - |
|
| 206 | - break; |
|
| 207 | - |
|
| 208 | - case 0x0C: |
|
| 209 | - // Monthly |
|
| 210 | - if (strlen($rdata) < 16) { |
|
| 211 | - return $ret; |
|
| 212 | - } |
|
| 213 | - |
|
| 214 | - $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata); |
|
| 215 | - |
|
| 216 | - $ret["everyn"] = $data["everyn"]; |
|
| 217 | - $ret["regen"] = $data["regen"]; |
|
| 218 | - |
|
| 219 | - if ($ret["subtype"] == 3) { |
|
| 220 | - $ret["weekdays"] = $data["monthday"]; |
|
| 221 | - } |
|
| 222 | - else { |
|
| 223 | - $ret["monthday"] = $data["monthday"]; |
|
| 224 | - } |
|
| 225 | - |
|
| 226 | - $rdata = substr($rdata, 16); |
|
| 227 | - |
|
| 228 | - if ($ret["subtype"] == 3) { |
|
| 229 | - $data = unpack("Vnday", $rdata); |
|
| 230 | - $ret["nday"] = $data["nday"]; |
|
| 231 | - $rdata = substr($rdata, 4); |
|
| 232 | - } |
|
| 233 | - |
|
| 234 | - break; |
|
| 235 | - |
|
| 236 | - case 0x0D: |
|
| 237 | - // Yearly |
|
| 238 | - if (strlen($rdata) < 16) { |
|
| 239 | - return $ret; |
|
| 240 | - } |
|
| 241 | - |
|
| 242 | - $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata); |
|
| 243 | - |
|
| 244 | - $ret["month"] = $data["month"]; |
|
| 245 | - $ret["everyn"] = $data["everyn"]; |
|
| 246 | - $ret["regen"] = $data["regen"]; |
|
| 247 | - |
|
| 248 | - if ($ret["subtype"] == 3) { |
|
| 249 | - $ret["weekdays"] = $data["monthday"]; |
|
| 250 | - } |
|
| 251 | - else { |
|
| 252 | - $ret["monthday"] = $data["monthday"]; |
|
| 253 | - } |
|
| 254 | - |
|
| 255 | - $rdata = substr($rdata, 16); |
|
| 256 | - |
|
| 257 | - if ($ret["subtype"] == 3) { |
|
| 258 | - $data = unpack("Vnday", $rdata); |
|
| 259 | - $ret["nday"] = $data["nday"]; |
|
| 260 | - $rdata = substr($rdata, 4); |
|
| 261 | - } |
|
| 262 | - |
|
| 263 | - break; |
|
| 264 | - } |
|
| 265 | - |
|
| 266 | - if (strlen($rdata) < 16) { |
|
| 267 | - return $ret; |
|
| 268 | - } |
|
| 269 | - |
|
| 270 | - $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata); |
|
| 271 | - |
|
| 272 | - $rdata = substr($rdata, 16); |
|
| 273 | - |
|
| 274 | - $ret["term"] = $data["term"]; |
|
| 275 | - $ret["numoccur"] = $data["numoccur"]; |
|
| 276 | - $ret["numexcept"] = $data["numexcept"]; |
|
| 277 | - |
|
| 278 | - // exc_base_dates are *all* the base dates that have been either deleted or modified |
|
| 279 | - $exc_base_dates = []; |
|
| 280 | - for ($i = 0; $i < $ret["numexcept"]; ++$i) { |
|
| 281 | - if (strlen($rdata) < 4) { |
|
| 282 | - // We shouldn't arrive here, because that implies |
|
| 283 | - // numexcept does not match the amount of data |
|
| 284 | - // which is available for the exceptions. |
|
| 285 | - return $ret; |
|
| 286 | - } |
|
| 287 | - $data = unpack("Vbasedate", $rdata); |
|
| 288 | - $rdata = substr($rdata, 4); |
|
| 289 | - $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]); |
|
| 290 | - } |
|
| 291 | - |
|
| 292 | - if (strlen($rdata) < 4) { |
|
| 293 | - return $ret; |
|
| 294 | - } |
|
| 295 | - |
|
| 296 | - $data = unpack("Vnumexceptmod", $rdata); |
|
| 297 | - $rdata = substr($rdata, 4); |
|
| 298 | - |
|
| 299 | - $ret["numexceptmod"] = $data["numexceptmod"]; |
|
| 300 | - |
|
| 301 | - // exc_changed are the base dates of *modified* occurrences. exactly what is modified |
|
| 302 | - // is in the attachments *and* in the data further down this function. |
|
| 303 | - $exc_changed = []; |
|
| 304 | - for ($i = 0; $i < $ret["numexceptmod"]; ++$i) { |
|
| 305 | - if (strlen($rdata) < 4) { |
|
| 306 | - // We shouldn't arrive here, because that implies |
|
| 307 | - // numexceptmod does not match the amount of data |
|
| 308 | - // which is available for the exceptions. |
|
| 309 | - return $ret; |
|
| 310 | - } |
|
| 311 | - $data = unpack("Vstartdate", $rdata); |
|
| 312 | - $rdata = substr($rdata, 4); |
|
| 313 | - $exc_changed[] = $this->recurDataToUnixData($data["startdate"]); |
|
| 314 | - } |
|
| 315 | - |
|
| 316 | - if (strlen($rdata) < 8) { |
|
| 317 | - return $ret; |
|
| 318 | - } |
|
| 319 | - |
|
| 320 | - $data = unpack("Vstart/Vend", $rdata); |
|
| 321 | - $rdata = substr($rdata, 8); |
|
| 322 | - |
|
| 323 | - $ret["start"] = $this->recurDataToUnixData($data["start"]); |
|
| 324 | - $ret["end"] = $this->recurDataToUnixData($data["end"]); |
|
| 325 | - |
|
| 326 | - // this is where task recurrence stop |
|
| 327 | - if (strlen($rdata) < 16) { |
|
| 328 | - return $ret; |
|
| 329 | - } |
|
| 330 | - |
|
| 331 | - $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata); |
|
| 332 | - $rdata = substr($rdata, 16); |
|
| 333 | - |
|
| 334 | - $ret["startocc"] = $data["startmin"]; |
|
| 335 | - $ret["endocc"] = $data["endmin"]; |
|
| 336 | - $writerversion = $data["writerversion"]; |
|
| 337 | - |
|
| 338 | - $data = unpack("vnumber", $rdata); |
|
| 339 | - $rdata = substr($rdata, 2); |
|
| 340 | - |
|
| 341 | - $nexceptions = $data["number"]; |
|
| 342 | - $exc_changed_details = []; |
|
| 343 | - |
|
| 344 | - // Parse n modified exceptions |
|
| 345 | - for ($i = 0; $i < $nexceptions; ++$i) { |
|
| 346 | - $item = []; |
|
| 347 | - |
|
| 348 | - // Get exception startdate, enddate and basedate (the date at which the occurrence would have started) |
|
| 349 | - $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata); |
|
| 350 | - $rdata = substr($rdata, 12); |
|
| 351 | - |
|
| 352 | - // Convert recurtimestamp to unix timestamp |
|
| 353 | - $startdate = $this->recurDataToUnixData($data["startdate"]); |
|
| 354 | - $enddate = $this->recurDataToUnixData($data["enddate"]); |
|
| 355 | - $basedate = $this->recurDataToUnixData($data["basedate"]); |
|
| 356 | - |
|
| 357 | - // Set the right properties |
|
| 358 | - $item["basedate"] = $this->dayStartOf($basedate); |
|
| 359 | - $item["start"] = $startdate; |
|
| 360 | - $item["end"] = $enddate; |
|
| 361 | - |
|
| 362 | - $data = unpack("vbitmask", $rdata); |
|
| 363 | - $rdata = substr($rdata, 2); |
|
| 364 | - $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions |
|
| 365 | - |
|
| 366 | - // Bitmask to verify what properties are changed |
|
| 367 | - $bitmask = $data["bitmask"]; |
|
| 368 | - |
|
| 369 | - // ARO_SUBJECT: 0x0001 |
|
| 370 | - // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject |
|
| 371 | - if (($bitmask & (1 << 0))) { |
|
| 372 | - $data = unpack("vnull_length/vlength", $rdata); |
|
| 373 | - $rdata = substr($rdata, 4); |
|
| 374 | - |
|
| 375 | - $length = $data["length"]; |
|
| 376 | - $item["subject"] = ""; // Normalized subject |
|
| 377 | - for ($j = 0; $j < $length && strlen($rdata); ++$j) { |
|
| 378 | - $data = unpack("Cchar", $rdata); |
|
| 379 | - $rdata = substr($rdata, 1); |
|
| 380 | - |
|
| 381 | - $item["subject"] .= chr($data["char"]); |
|
| 382 | - } |
|
| 383 | - } |
|
| 384 | - |
|
| 385 | - // ARO_MEETINGTYPE: 0x0002 |
|
| 386 | - if (($bitmask & (1 << 1))) { |
|
| 387 | - $rdata = substr($rdata, 4); |
|
| 388 | - // Attendees modified: no data here (only in attachment) |
|
| 389 | - } |
|
| 390 | - |
|
| 391 | - // ARO_REMINDERDELTA: 0x0004 |
|
| 392 | - // Look for field: ReminderDelta (4b) |
|
| 393 | - if (($bitmask & (1 << 2))) { |
|
| 394 | - $data = unpack("Vremind_before", $rdata); |
|
| 395 | - $rdata = substr($rdata, 4); |
|
| 396 | - |
|
| 397 | - $item["remind_before"] = $data["remind_before"]; |
|
| 398 | - } |
|
| 399 | - |
|
| 400 | - // ARO_REMINDER: 0x0008 |
|
| 401 | - // Look field: ReminderSet (4b) |
|
| 402 | - if (($bitmask & (1 << 3))) { |
|
| 403 | - $data = unpack("Vreminder_set", $rdata); |
|
| 404 | - $rdata = substr($rdata, 4); |
|
| 405 | - |
|
| 406 | - $item["reminder_set"] = $data["reminder_set"]; |
|
| 407 | - } |
|
| 408 | - |
|
| 409 | - // ARO_LOCATION: 0x0010 |
|
| 410 | - // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location |
|
| 411 | - // Similar to ARO_SUBJECT above. |
|
| 412 | - if (($bitmask & (1 << 4))) { |
|
| 413 | - $data = unpack("vnull_length/vlength", $rdata); |
|
| 414 | - $rdata = substr($rdata, 4); |
|
| 415 | - |
|
| 416 | - $item["location"] = ""; |
|
| 417 | - |
|
| 418 | - $length = $data["length"]; |
|
| 419 | - $data = substr($rdata, 0, $length); |
|
| 420 | - $rdata = substr($rdata, $length); |
|
| 421 | - |
|
| 422 | - $item["location"] .= $data; |
|
| 423 | - } |
|
| 424 | - |
|
| 425 | - // ARO_BUSYSTATUS: 0x0020 |
|
| 426 | - // Look for field: BusyStatus (4b) |
|
| 427 | - if (($bitmask & (1 << 5))) { |
|
| 428 | - $data = unpack("Vbusystatus", $rdata); |
|
| 429 | - $rdata = substr($rdata, 4); |
|
| 430 | - |
|
| 431 | - $item["busystatus"] = $data["busystatus"]; |
|
| 432 | - } |
|
| 433 | - |
|
| 434 | - // ARO_ATTACHMENT: 0x0040 |
|
| 435 | - if (($bitmask & (1 << 6))) { |
|
| 436 | - // no data: RESERVED |
|
| 437 | - $rdata = substr($rdata, 4); |
|
| 438 | - } |
|
| 439 | - |
|
| 440 | - // ARO_SUBTYPE: 0x0080 |
|
| 441 | - // Look for field: SubType (4b). Determines whether it is an allday event. |
|
| 442 | - if (($bitmask & (1 << 7))) { |
|
| 443 | - $data = unpack("Vallday", $rdata); |
|
| 444 | - $rdata = substr($rdata, 4); |
|
| 445 | - |
|
| 446 | - $item["alldayevent"] = $data["allday"]; |
|
| 447 | - } |
|
| 448 | - |
|
| 449 | - // ARO_APPTCOLOR: 0x0100 |
|
| 450 | - // Look for field: AppointmentColor (4b) |
|
| 451 | - if (($bitmask & (1 << 8))) { |
|
| 452 | - $data = unpack("Vlabel", $rdata); |
|
| 453 | - $rdata = substr($rdata, 4); |
|
| 454 | - |
|
| 455 | - $item["label"] = $data["label"]; |
|
| 456 | - } |
|
| 457 | - |
|
| 458 | - // ARO_EXCEPTIONAL_BODY: 0x0200 |
|
| 459 | - if (($bitmask & (1 << 9))) { |
|
| 460 | - // Notes or Attachments modified: no data here (only in attachment) |
|
| 461 | - } |
|
| 462 | - |
|
| 463 | - array_push($exc_changed_details, $item); |
|
| 464 | - } |
|
| 465 | - |
|
| 466 | - /** |
|
| 467 | - * We now have $exc_changed, $exc_base_dates and $exc_changed_details |
|
| 468 | - * We will ignore $exc_changed, as this information is available in $exc_changed_details |
|
| 469 | - * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item |
|
| 470 | - * has been deleted. |
|
| 471 | - */ |
|
| 472 | - |
|
| 473 | - // Find deleted occurrences |
|
| 474 | - $deleted_occurrences = []; |
|
| 475 | - |
|
| 476 | - foreach ($exc_base_dates as $base_date) { |
|
| 477 | - $found = false; |
|
| 478 | - |
|
| 479 | - foreach ($exc_changed_details as $details) { |
|
| 480 | - if ($details["basedate"] == $base_date) { |
|
| 481 | - $found = true; |
|
| 482 | - |
|
| 483 | - break; |
|
| 484 | - } |
|
| 485 | - } |
|
| 486 | - if (!$found) { |
|
| 487 | - // item was not in exc_changed_details, so it must be deleted |
|
| 488 | - $deleted_occurrences[] = $base_date; |
|
| 489 | - } |
|
| 490 | - } |
|
| 491 | - |
|
| 492 | - $ret["deleted_occurrences"] = $deleted_occurrences; |
|
| 493 | - $ret["changed_occurrences"] = $exc_changed_details; |
|
| 494 | - |
|
| 495 | - // enough data for normal exception (no extended data) |
|
| 496 | - if (strlen($rdata) < 16) { |
|
| 497 | - return $ret; |
|
| 498 | - } |
|
| 499 | - |
|
| 500 | - $data = unpack("Vreservedsize", $rdata); |
|
| 501 | - $rdata = substr($rdata, 4 + $data["reservedsize"]); |
|
| 502 | - |
|
| 503 | - for ($i = 0; $i < $nexceptions; ++$i) { |
|
| 504 | - // subject and location in ucs-2 to utf-8 |
|
| 505 | - if ($writerversion >= 0x3009) { |
|
| 506 | - $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4 |
|
| 507 | - $rdata = substr($rdata, 4 + $data["size"]); |
|
| 508 | - } |
|
| 509 | - |
|
| 510 | - $data = unpack("Vreservedsize", $rdata); |
|
| 511 | - $rdata = substr($rdata, 4 + $data["reservedsize"]); |
|
| 512 | - |
|
| 513 | - // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10) |
|
| 514 | - if ($exc_changed_details[$i]["bitmask"] & 0x11) { |
|
| 515 | - $data = unpack("Vstart/Vend/Vorig", $rdata); |
|
| 516 | - $rdata = substr($rdata, 4 * 3); |
|
| 517 | - |
|
| 518 | - $exc_changed_details[$i]["ex_start_datetime"] = $data["start"]; |
|
| 519 | - $exc_changed_details[$i]["ex_end_datetime"] = $data["end"]; |
|
| 520 | - $exc_changed_details[$i]["ex_orig_date"] = $data["orig"]; |
|
| 521 | - } |
|
| 522 | - |
|
| 523 | - // ARO_SUBJECT |
|
| 524 | - if ($exc_changed_details[$i]["bitmask"] & 0x01) { |
|
| 525 | - // decode ucs2 string to utf-8 |
|
| 526 | - $data = unpack("vlength", $rdata); |
|
| 527 | - $rdata = substr($rdata, 2); |
|
| 528 | - $length = $data["length"]; |
|
| 529 | - $data = substr($rdata, 0, $length * 2); |
|
| 530 | - $rdata = substr($rdata, $length * 2); |
|
| 531 | - $subject = iconv("UCS-2LE", "UTF-8", $data); |
|
| 532 | - // replace subject with unicode subject |
|
| 533 | - $exc_changed_details[$i]["subject"] = $subject; |
|
| 534 | - } |
|
| 535 | - |
|
| 536 | - // ARO_LOCATION |
|
| 537 | - if ($exc_changed_details[$i]["bitmask"] & 0x10) { |
|
| 538 | - // decode ucs2 string to utf-8 |
|
| 539 | - $data = unpack("vlength", $rdata); |
|
| 540 | - $rdata = substr($rdata, 2); |
|
| 541 | - $length = $data["length"]; |
|
| 542 | - $data = substr($rdata, 0, $length * 2); |
|
| 543 | - $rdata = substr($rdata, $length * 2); |
|
| 544 | - $location = iconv("UCS-2LE", "UTF-8", $data); |
|
| 545 | - // replace subject with unicode subject |
|
| 546 | - $exc_changed_details[$i]["location"] = $location; |
|
| 547 | - } |
|
| 548 | - |
|
| 549 | - // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10) |
|
| 550 | - if ($exc_changed_details[$i]["bitmask"] & 0x11) { |
|
| 551 | - $data = unpack("Vreservedsize", $rdata); |
|
| 552 | - $rdata = substr($rdata, 4 + $data["reservedsize"]); |
|
| 553 | - } |
|
| 554 | - } |
|
| 555 | - |
|
| 556 | - // update with extended data |
|
| 557 | - $ret["changed_occurrences"] = $exc_changed_details; |
|
| 558 | - |
|
| 559 | - return $ret; |
|
| 560 | - } |
|
| 561 | - |
|
| 562 | - /** |
|
| 563 | - * Saves the recurrence data to the recurrence property. |
|
| 564 | - */ |
|
| 565 | - public function saveRecurrence() { |
|
| 566 | - // Only save if a message was passed |
|
| 567 | - if (!isset($this->message)) { |
|
| 568 | - return; |
|
| 569 | - } |
|
| 570 | - |
|
| 571 | - // Abort if no recurrence was set |
|
| 572 | - if (!isset($this->recur["type"], $this->recur["subtype"], $this->recur["start"], $this->recur["end"], $this->recur["startocc"], $this->recur["endocc"])) { |
|
| 573 | - return; |
|
| 574 | - } |
|
| 575 | - |
|
| 576 | - $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]); |
|
| 577 | - |
|
| 578 | - $weekstart = 1; // monday |
|
| 579 | - $forwardcount = 0; |
|
| 580 | - $restocc = 0; |
|
| 581 | - $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday) |
|
| 582 | - |
|
| 583 | - $term = (int) $this->recur["type"]; |
|
| 584 | - |
|
| 585 | - switch ($term) { |
|
| 586 | - case 0x0A: |
|
| 587 | - // Daily |
|
| 588 | - if (!isset($this->recur["everyn"])) { |
|
| 589 | - return; |
|
| 590 | - } |
|
| 591 | - |
|
| 592 | - if ($this->recur["subtype"] == 1) { |
|
| 593 | - // Daily every workday |
|
| 594 | - $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E); |
|
| 595 | - } |
|
| 596 | - else { |
|
| 597 | - // Daily every N days (everyN in minutes) |
|
| 598 | - |
|
| 599 | - $everyn = ((int) $this->recur["everyn"]) / 1440; |
|
| 600 | - |
|
| 601 | - // Calc first occ |
|
| 602 | - $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]); |
|
| 603 | - |
|
| 604 | - $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0); |
|
| 605 | - } |
|
| 606 | - |
|
| 607 | - break; |
|
| 608 | - |
|
| 609 | - case 0x0B: |
|
| 610 | - // Weekly |
|
| 611 | - if (!isset($this->recur["everyn"])) { |
|
| 612 | - return; |
|
| 613 | - } |
|
| 614 | - |
|
| 615 | - if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) { |
|
| 616 | - return; |
|
| 617 | - } |
|
| 618 | - |
|
| 619 | - // No need to calculate startdate if sliding flag was set. |
|
| 620 | - if (!$this->recur['regen']) { |
|
| 621 | - // Calculate start date of recurrence |
|
| 622 | - |
|
| 623 | - // Find the first day that matches one of the weekdays selected |
|
| 624 | - $daycount = 0; |
|
| 625 | - $dayskip = -1; |
|
| 626 | - for ($j = 0; $j < 7; ++$j) { |
|
| 627 | - if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 628 | - if ($dayskip == -1) { |
|
| 629 | - $dayskip = $j; |
|
| 630 | - } |
|
| 631 | - |
|
| 632 | - ++$daycount; |
|
| 633 | - } |
|
| 634 | - } |
|
| 635 | - |
|
| 636 | - // $dayskip is the number of days to skip from the startdate until the first occurrence |
|
| 637 | - // $daycount is the number of days per week that an occurrence occurs |
|
| 638 | - |
|
| 639 | - $weekskip = 0; |
|
| 640 | - if (($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek + $dayskip) > 6) { |
|
| 641 | - $weekskip = 1; |
|
| 642 | - } |
|
| 643 | - |
|
| 644 | - // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
|
| 645 | - // remaining occurrences based on the start of the recurrence. |
|
| 646 | - if (((int) $this->recur["term"]) == 0x22) { |
|
| 647 | - // $weekskip is the amount of weeks to skip from the startdate before the first occurrence |
|
| 648 | - // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that |
|
| 649 | - // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over |
|
| 650 | - // (eg when numoccur = 2, and daycount = 1) |
|
| 651 | - $forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount); |
|
| 652 | - |
|
| 653 | - // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one |
|
| 654 | - // for the occurrence on the first day |
|
| 655 | - $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1; |
|
| 656 | - |
|
| 657 | - // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence |
|
| 658 | - $forwardcount *= (int) $this->recur["everyn"]; |
|
| 659 | - } |
|
| 660 | - |
|
| 661 | - // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week) |
|
| 662 | - $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60); |
|
| 663 | - } |
|
| 664 | - |
|
| 665 | - // Calc first occ |
|
| 666 | - $firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60); |
|
| 667 | - |
|
| 668 | - $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60; |
|
| 669 | - |
|
| 670 | - if ($this->recur["regen"]) { |
|
| 671 | - $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1); |
|
| 672 | - } |
|
| 673 | - else { |
|
| 674 | - $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]); |
|
| 675 | - } |
|
| 676 | - |
|
| 677 | - break; |
|
| 678 | - |
|
| 679 | - case 0x0C: |
|
| 680 | - // Monthly |
|
| 681 | - case 0x0D: |
|
| 682 | - // Yearly |
|
| 683 | - if (!isset($this->recur["everyn"])) { |
|
| 684 | - return; |
|
| 685 | - } |
|
| 686 | - if ($term == 0x0D /* yearly */ && !isset($this->recur["month"])) { |
|
| 687 | - return; |
|
| 688 | - } |
|
| 689 | - |
|
| 690 | - if ($term == 0x0C /* monthly */) { |
|
| 691 | - $everyn = (int) $this->recur["everyn"]; |
|
| 692 | - } |
|
| 693 | - else { |
|
| 694 | - $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12; |
|
| 695 | - } |
|
| 696 | - |
|
| 697 | - // Get montday/month/year of original start |
|
| 698 | - $curmonthday = gmdate("j", (int) $this->recur["start"]); |
|
| 699 | - $curyear = gmdate("Y", (int) $this->recur["start"]); |
|
| 700 | - $curmonth = gmdate("n", (int) $this->recur["start"]); |
|
| 701 | - |
|
| 702 | - // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
|
| 703 | - // remaining occurrences based on the start of the recurrence. |
|
| 704 | - if (((int) $this->recur["term"]) == 0x22) { |
|
| 705 | - // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus |
|
| 706 | - // one to make sure there are always at least one occurrence left) |
|
| 707 | - $forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn); |
|
| 708 | - } |
|
| 709 | - |
|
| 710 | - // Get month for yearly on D'th day of month M |
|
| 711 | - if ($term == 0x0D /* yearly */) { |
|
| 712 | - $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg |
|
| 713 | - } |
|
| 714 | - |
|
| 715 | - switch ((int) $this->recur["subtype"]) { |
|
| 716 | - // on D day of every M month |
|
| 717 | - case 2: |
|
| 718 | - if (!isset($this->recur["monthday"])) { |
|
| 719 | - return; |
|
| 720 | - } |
|
| 721 | - // Recalc startdate |
|
| 722 | - |
|
| 723 | - // Set on the right begin day |
|
| 724 | - |
|
| 725 | - // Go the beginning of the month |
|
| 726 | - $this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60; |
|
| 727 | - // Go the the correct month day |
|
| 728 | - $this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60; |
|
| 729 | - |
|
| 730 | - // If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence |
|
| 731 | - if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) || |
|
| 732 | - ($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) { |
|
| 733 | - if ($term == 0x0D /* yearly */) { |
|
| 734 | - if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date |
|
| 735 | - $count = $everyn - ($curmonth - $selmonth); |
|
| 736 | - } |
|
| 737 | - elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date |
|
| 738 | - $count = $selmonth - $curmonth; |
|
| 739 | - } |
|
| 740 | - else { |
|
| 741 | - // Go to next occurrence while recurrence start date is greater than occurrence date but within same month |
|
| 742 | - if (((int) $this->recur["monthday"]) < $curmonthday) { |
|
| 743 | - $count = $everyn; |
|
| 744 | - } |
|
| 745 | - } |
|
| 746 | - } |
|
| 747 | - else { |
|
| 748 | - $count = $everyn; // Monthly, go to next occurrence in 'everyn' months |
|
| 749 | - } |
|
| 750 | - |
|
| 751 | - // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days |
|
| 752 | - for ($i = 0; $i < $count; ++$i) { |
|
| 753 | - $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 754 | - |
|
| 755 | - if ($curmonth == 12) { |
|
| 756 | - ++$curyear; |
|
| 757 | - $curmonth = 0; |
|
| 758 | - } |
|
| 759 | - ++$curmonth; |
|
| 760 | - } |
|
| 761 | - } |
|
| 762 | - |
|
| 763 | - // "start" is now pointing to the first occurrence, except that it will overshoot if the |
|
| 764 | - // month in which it occurs has less days than specified as the day of the month. So 31st |
|
| 765 | - // of each month will overshoot in february (29 days). We compensate for that by checking |
|
| 766 | - // if the day of the month we got is wrong, and then back up to the last day of the previous |
|
| 767 | - // month. |
|
| 768 | - if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 && |
|
| 769 | - gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) { |
|
| 770 | - $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60; |
|
| 771 | - } |
|
| 772 | - |
|
| 773 | - // "start" is now the first occurrence |
|
| 774 | - |
|
| 775 | - if ($term == 0x0C /* monthly */) { |
|
| 776 | - // Calc first occ |
|
| 777 | - $monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 778 | - |
|
| 779 | - $firstocc = 0; |
|
| 780 | - for ($i = 0; $i < $monthIndex; ++$i) { |
|
| 781 | - $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60; |
|
| 782 | - } |
|
| 783 | - |
|
| 784 | - $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
|
| 785 | - } |
|
| 786 | - else { |
|
| 787 | - // Calc first occ |
|
| 788 | - $firstocc = 0; |
|
| 789 | - $monthIndex = (int) gmdate("n", $this->recur["start"]); |
|
| 790 | - for ($i = 1; $i < $monthIndex; ++$i) { |
|
| 791 | - $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; |
|
| 792 | - } |
|
| 793 | - |
|
| 794 | - $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
|
| 795 | - } |
|
| 796 | - |
|
| 797 | - break; |
|
| 798 | - |
|
| 799 | - case 3: |
|
| 800 | - // monthly: on Nth weekday of every M month |
|
| 801 | - // yearly: on Nth weekday of M month |
|
| 802 | - if (!isset($this->recur["weekdays"], $this->recur["nday"])) { |
|
| 803 | - return; |
|
| 804 | - } |
|
| 805 | - |
|
| 806 | - $weekdays = (int) $this->recur["weekdays"]; |
|
| 807 | - $nday = (int) $this->recur["nday"]; |
|
| 808 | - |
|
| 809 | - // Calc startdate |
|
| 810 | - $monthbegindow = (int) $this->recur["start"]; |
|
| 811 | - |
|
| 812 | - if ($nday == 5) { |
|
| 813 | - // Set date on the last day of the last month |
|
| 814 | - $monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60; |
|
| 815 | - } |
|
| 816 | - else { |
|
| 817 | - // Set on the first day of the month |
|
| 818 | - $monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60); |
|
| 819 | - } |
|
| 820 | - |
|
| 821 | - if ($term == 0x0D /* yearly */) { |
|
| 822 | - // Set on right month |
|
| 823 | - if ($selmonth < $curmonth) { |
|
| 824 | - $tmp = 12 - $curmonth + $selmonth; |
|
| 825 | - } |
|
| 826 | - else { |
|
| 827 | - $tmp = ($selmonth - $curmonth); |
|
| 828 | - } |
|
| 829 | - |
|
| 830 | - for ($i = 0; $i < $tmp; ++$i) { |
|
| 831 | - $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 832 | - |
|
| 833 | - if ($curmonth == 12) { |
|
| 834 | - ++$curyear; |
|
| 835 | - $curmonth = 0; |
|
| 836 | - } |
|
| 837 | - ++$curmonth; |
|
| 838 | - } |
|
| 839 | - } |
|
| 840 | - else { |
|
| 841 | - // Check or you exist in the right month |
|
| 842 | - |
|
| 843 | - $dayofweek = gmdate("w", $monthbegindow); |
|
| 844 | - for ($i = 0; $i < 7; ++$i) { |
|
| 845 | - if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 846 | - $day = gmdate("j", $monthbegindow) - $i; |
|
| 847 | - |
|
| 848 | - break; |
|
| 849 | - } |
|
| 850 | - if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 851 | - $day = (($nday - 1) * 7) + ($i + 1); |
|
| 852 | - |
|
| 853 | - break; |
|
| 854 | - } |
|
| 855 | - } |
|
| 856 | - |
|
| 857 | - // Goto the next X month |
|
| 858 | - if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) { |
|
| 859 | - if ($nday == 5) { |
|
| 860 | - $monthbegindow += 24 * 60 * 60; |
|
| 861 | - if ($curmonth == 12) { |
|
| 862 | - ++$curyear; |
|
| 863 | - $curmonth = 0; |
|
| 864 | - } |
|
| 865 | - ++$curmonth; |
|
| 866 | - } |
|
| 867 | - |
|
| 868 | - for ($i = 0; $i < $everyn; ++$i) { |
|
| 869 | - $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 870 | - |
|
| 871 | - if ($curmonth == 12) { |
|
| 872 | - ++$curyear; |
|
| 873 | - $curmonth = 0; |
|
| 874 | - } |
|
| 875 | - ++$curmonth; |
|
| 876 | - } |
|
| 877 | - |
|
| 878 | - if ($nday == 5) { |
|
| 879 | - $monthbegindow -= 24 * 60 * 60; |
|
| 880 | - } |
|
| 881 | - } |
|
| 882 | - } |
|
| 883 | - |
|
| 884 | - // FIXME: weekstart? |
|
| 885 | - |
|
| 886 | - $day = 0; |
|
| 887 | - // Set start on the right day |
|
| 888 | - $dayofweek = gmdate("w", $monthbegindow); |
|
| 889 | - for ($i = 0; $i < 7; ++$i) { |
|
| 890 | - if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 891 | - $day = $i; |
|
| 892 | - |
|
| 893 | - break; |
|
| 894 | - } |
|
| 895 | - if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 896 | - $day = ($nday - 1) * 7 + ($i + 1); |
|
| 897 | - |
|
| 898 | - break; |
|
| 899 | - } |
|
| 900 | - } |
|
| 901 | - if ($nday == 5) { |
|
| 902 | - $monthbegindow -= $day * 24 * 60 * 60; |
|
| 903 | - } |
|
| 904 | - else { |
|
| 905 | - $monthbegindow += ($day - 1) * 24 * 60 * 60; |
|
| 906 | - } |
|
| 907 | - |
|
| 908 | - $firstocc = 0; |
|
| 909 | - |
|
| 910 | - if ($term == 0x0C /* monthly */) { |
|
| 911 | - // Calc first occ |
|
| 912 | - $monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 913 | - |
|
| 914 | - for ($i = 0; $i < $monthIndex; ++$i) { |
|
| 915 | - $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60; |
|
| 916 | - } |
|
| 917 | - |
|
| 918 | - $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
|
| 919 | - } |
|
| 920 | - else { |
|
| 921 | - // Calc first occ |
|
| 922 | - $monthIndex = (int) gmdate("n", $this->recur["start"]); |
|
| 923 | - |
|
| 924 | - for ($i = 1; $i < $monthIndex; ++$i) { |
|
| 925 | - $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; |
|
| 926 | - } |
|
| 927 | - |
|
| 928 | - $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
|
| 929 | - } |
|
| 930 | - |
|
| 931 | - break; |
|
| 932 | - } |
|
| 933 | - |
|
| 934 | - break; |
|
| 935 | - } |
|
| 936 | - |
|
| 937 | - if (!isset($this->recur["term"])) { |
|
| 938 | - return; |
|
| 939 | - } |
|
| 940 | - |
|
| 941 | - // Terminate |
|
| 942 | - $term = (int) $this->recur["term"]; |
|
| 943 | - $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00); |
|
| 944 | - |
|
| 945 | - switch ($term) { |
|
| 946 | - // After the given enddate |
|
| 947 | - case 0x21: |
|
| 948 | - $rdata .= pack("V", 10); |
|
| 949 | - |
|
| 950 | - break; |
|
| 951 | - // After a number of times |
|
| 952 | - case 0x22: |
|
| 953 | - if (!isset($this->recur["numoccur"])) { |
|
| 954 | - return; |
|
| 955 | - } |
|
| 956 | - |
|
| 957 | - $rdata .= pack("V", (int) $this->recur["numoccur"]); |
|
| 958 | - |
|
| 959 | - break; |
|
| 960 | - // Never ends |
|
| 961 | - case 0x23: |
|
| 962 | - $rdata .= pack("V", 0); |
|
| 963 | - |
|
| 964 | - break; |
|
| 965 | - } |
|
| 966 | - |
|
| 967 | - // Strange little thing for the recurrence type "every workday" |
|
| 968 | - if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) { |
|
| 969 | - $rdata .= pack("V", 1); |
|
| 970 | - } |
|
| 971 | - else { // Other recurrences |
|
| 972 | - $rdata .= pack("V", 0); |
|
| 973 | - } |
|
| 974 | - |
|
| 975 | - // Exception data |
|
| 976 | - |
|
| 977 | - // Get all exceptions |
|
| 978 | - $deleted_items = $this->recur["deleted_occurrences"]; |
|
| 979 | - $changed_items = $this->recur["changed_occurrences"]; |
|
| 980 | - |
|
| 981 | - // Merge deleted and changed items into one list |
|
| 982 | - $items = $deleted_items; |
|
| 983 | - |
|
| 984 | - foreach ($changed_items as $changed_item) { |
|
| 985 | - array_push($items, $changed_item["basedate"]); |
|
| 986 | - } |
|
| 987 | - |
|
| 988 | - sort($items); |
|
| 989 | - |
|
| 990 | - // Add the merged list in to the rdata |
|
| 991 | - $rdata .= pack("V", count($items)); |
|
| 992 | - foreach ($items as $item) { |
|
| 993 | - $rdata .= pack("V", $this->unixDataToRecurData($item)); |
|
| 994 | - } |
|
| 995 | - |
|
| 996 | - // Loop through the changed exceptions (not deleted) |
|
| 997 | - $rdata .= pack("V", count($changed_items)); |
|
| 998 | - $items = []; |
|
| 999 | - |
|
| 1000 | - foreach ($changed_items as $changed_item) { |
|
| 1001 | - $items[] = $this->dayStartOf($changed_item["start"]); |
|
| 1002 | - } |
|
| 1003 | - |
|
| 1004 | - sort($items); |
|
| 1005 | - |
|
| 1006 | - // Add the changed items list int the rdata |
|
| 1007 | - foreach ($items as $item) { |
|
| 1008 | - $rdata .= pack("V", $this->unixDataToRecurData($item)); |
|
| 1009 | - } |
|
| 1010 | - |
|
| 1011 | - // Set start date |
|
| 1012 | - $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"])); |
|
| 1013 | - |
|
| 1014 | - // Set enddate |
|
| 1015 | - switch ($term) { |
|
| 1016 | - // After the given enddate |
|
| 1017 | - case 0x21: |
|
| 1018 | - $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
|
| 1019 | - |
|
| 1020 | - break; |
|
| 1021 | - // After a number of times |
|
| 1022 | - case 0x22: |
|
| 1023 | - // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour |
|
| 1024 | - $occenddate = (int) $this->recur["start"]; |
|
| 1025 | - |
|
| 1026 | - switch ((int) $this->recur["type"]) { |
|
| 1027 | - case 0x0A: // daily |
|
| 1028 | - if ($this->recur["subtype"] == 1) { |
|
| 1029 | - // Daily every workday |
|
| 1030 | - $restocc = (int) $this->recur["numoccur"]; |
|
| 1031 | - |
|
| 1032 | - // Get starting weekday |
|
| 1033 | - $nowtime = $this->gmtime($occenddate); |
|
| 1034 | - $j = $nowtime["tm_wday"]; |
|
| 1035 | - |
|
| 1036 | - while (1) { |
|
| 1037 | - if (($j % 7) > 0 && ($j % 7) < 6) { |
|
| 1038 | - --$restocc; |
|
| 1039 | - } |
|
| 1040 | - |
|
| 1041 | - ++$j; |
|
| 1042 | - |
|
| 1043 | - if ($restocc <= 0) { |
|
| 1044 | - break; |
|
| 1045 | - } |
|
| 1046 | - |
|
| 1047 | - $occenddate += 24 * 60 * 60; |
|
| 1048 | - } |
|
| 1049 | - } |
|
| 1050 | - else { |
|
| 1051 | - // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence) |
|
| 1052 | - $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1))); |
|
| 1053 | - } |
|
| 1054 | - |
|
| 1055 | - break; |
|
| 1056 | - |
|
| 1057 | - case 0x0B: // weekly |
|
| 1058 | - // Needed values |
|
| 1059 | - // $forwardcount - number of weeks we can skip forward |
|
| 1060 | - // $restocc - number of remaining occurrences after the week skip |
|
| 1061 | - |
|
| 1062 | - // Add the weeks till the last item |
|
| 1063 | - $occenddate += ($forwardcount * 7 * 24 * 60 * 60); |
|
| 1064 | - |
|
| 1065 | - $dayofweek = gmdate("w", $occenddate); |
|
| 1066 | - |
|
| 1067 | - // Loop through the last occurrences until we have had them all |
|
| 1068 | - for ($j = 1; $restocc > 0; ++$j) { |
|
| 1069 | - // Jump to the next week (which may be N weeks away) when going over the week boundary |
|
| 1070 | - if ((($dayofweek + $j) % 7) == $weekstart) { |
|
| 1071 | - $occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60; |
|
| 1072 | - } |
|
| 1073 | - |
|
| 1074 | - // If this is a matching day, once less occurrence to process |
|
| 1075 | - if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 1076 | - --$restocc; |
|
| 1077 | - } |
|
| 1078 | - |
|
| 1079 | - // Next day |
|
| 1080 | - $occenddate += 24 * 60 * 60; |
|
| 1081 | - } |
|
| 1082 | - |
|
| 1083 | - break; |
|
| 1084 | - |
|
| 1085 | - case 0x0C: // monthly |
|
| 1086 | - case 0x0D: // yearly |
|
| 1087 | - $curyear = gmdate("Y", (int) $this->recur["start"]); |
|
| 1088 | - $curmonth = gmdate("n", (int) $this->recur["start"]); |
|
| 1089 | - // $forwardcount = months |
|
| 1090 | - |
|
| 1091 | - switch ((int) $this->recur["subtype"]) { |
|
| 1092 | - case 2: // on D day of every M month |
|
| 1093 | - while ($forwardcount > 0) { |
|
| 1094 | - $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 1095 | - |
|
| 1096 | - if ($curmonth >= 12) { |
|
| 1097 | - $curmonth = 1; |
|
| 1098 | - ++$curyear; |
|
| 1099 | - } |
|
| 1100 | - else { |
|
| 1101 | - ++$curmonth; |
|
| 1102 | - } |
|
| 1103 | - --$forwardcount; |
|
| 1104 | - } |
|
| 1105 | - |
|
| 1106 | - // compensation between 28 and 31 |
|
| 1107 | - if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 && |
|
| 1108 | - gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) { |
|
| 1109 | - if (gmdate("j", $occenddate) < 28) { |
|
| 1110 | - $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60; |
|
| 1111 | - } |
|
| 1112 | - else { |
|
| 1113 | - $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; |
|
| 1114 | - } |
|
| 1115 | - } |
|
| 1116 | - |
|
| 1117 | - break; |
|
| 1118 | - |
|
| 1119 | - case 3: // on Nth weekday of every M month |
|
| 1120 | - $nday = (int) $this->recur["nday"]; // 1 tot 5 |
|
| 1121 | - $weekdays = (int) $this->recur["weekdays"]; |
|
| 1122 | - |
|
| 1123 | - while ($forwardcount > 0) { |
|
| 1124 | - $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 1125 | - if ($curmonth >= 12) { |
|
| 1126 | - $curmonth = 1; |
|
| 1127 | - ++$curyear; |
|
| 1128 | - } |
|
| 1129 | - else { |
|
| 1130 | - ++$curmonth; |
|
| 1131 | - } |
|
| 1132 | - |
|
| 1133 | - --$forwardcount; |
|
| 1134 | - } |
|
| 1135 | - |
|
| 1136 | - if ($nday == 5) { |
|
| 1137 | - // Set date on the last day of the last month |
|
| 1138 | - $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; |
|
| 1139 | - } |
|
| 1140 | - else { |
|
| 1141 | - // Set date on the first day of the last month |
|
| 1142 | - $occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60; |
|
| 1143 | - } |
|
| 1144 | - |
|
| 1145 | - $dayofweek = gmdate("w", $occenddate); |
|
| 1146 | - for ($i = 0; $i < 7; ++$i) { |
|
| 1147 | - if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 1148 | - $occenddate -= $i * 24 * 60 * 60; |
|
| 1149 | - |
|
| 1150 | - break; |
|
| 1151 | - } |
|
| 1152 | - if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 1153 | - $occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60; |
|
| 1154 | - |
|
| 1155 | - break; |
|
| 1156 | - } |
|
| 1157 | - } |
|
| 1158 | - |
|
| 1159 | - break; // case 3: |
|
| 1160 | - } |
|
| 1161 | - |
|
| 1162 | - break; |
|
| 1163 | - } |
|
| 1164 | - |
|
| 1165 | - if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) { |
|
| 1166 | - $occenddate = PHP_INT_MAX; |
|
| 1167 | - } |
|
| 1168 | - |
|
| 1169 | - $this->recur["end"] = $occenddate; |
|
| 1170 | - |
|
| 1171 | - $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
|
| 1172 | - |
|
| 1173 | - break; |
|
| 1174 | - // Never ends |
|
| 1175 | - case 0x23: |
|
| 1176 | - default: |
|
| 1177 | - $this->recur["end"] = 0x7FFFFFFF; // max date -> 2038 |
|
| 1178 | - $rdata .= pack("V", 0x5AE980DF); |
|
| 1179 | - |
|
| 1180 | - break; |
|
| 1181 | - } |
|
| 1182 | - |
|
| 1183 | - // UTC date |
|
| 1184 | - $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]); |
|
| 1185 | - $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]); |
|
| 1186 | - |
|
| 1187 | - // utc date+time |
|
| 1188 | - $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart; |
|
| 1189 | - $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart; |
|
| 1190 | - |
|
| 1191 | - $propsToSet = []; |
|
| 1192 | - // update reminder time |
|
| 1193 | - $propsToSet[$this->proptags["reminder_time"]] = $utcfirstoccstartdatetime; |
|
| 1194 | - |
|
| 1195 | - // update first occurrence date |
|
| 1196 | - $propsToSet[$this->proptags["startdate"]] = $propsToSet[$this->proptags["commonstart"]] = $utcfirstoccstartdatetime; |
|
| 1197 | - $propsToSet[$this->proptags["duedate"]] = $propsToSet[$this->proptags["commonend"]] = $utcfirstoccenddatetime; |
|
| 1198 | - |
|
| 1199 | - // Set Outlook properties, if it is an appointment |
|
| 1200 | - if (isset($this->messageprops[$this->proptags["message_class"]]) && $this->messageprops[$this->proptags["message_class"]] == "IPM.Appointment") { |
|
| 1201 | - // update real begin and real end date |
|
| 1202 | - $propsToSet[$this->proptags["startdate_recurring"]] = $utcstart; |
|
| 1203 | - $propsToSet[$this->proptags["enddate_recurring"]] = $utcend; |
|
| 1204 | - |
|
| 1205 | - // recurrencetype |
|
| 1206 | - // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype |
|
| 1207 | - $propsToSet[$this->proptags["recurrencetype"]] = ((int) $this->recur["type"]) - 0x9; |
|
| 1208 | - |
|
| 1209 | - // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting |
|
| 1210 | - $propsToSet[$this->proptags["side_effects"]] = 369; |
|
| 1211 | - } |
|
| 1212 | - else { |
|
| 1213 | - $propsToSet[$this->proptags["side_effects"]] = 3441; |
|
| 1214 | - } |
|
| 1215 | - |
|
| 1216 | - // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog |
|
| 1217 | - // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset |
|
| 1218 | - // to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to |
|
| 1219 | - // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time) |
|
| 1220 | - // with the reminder flag set. |
|
| 1221 | - $reminderprops = mapi_getprops($this->message, [$this->proptags["reminder_minutes"]]); |
|
| 1222 | - if (isset($reminderprops[$this->proptags["reminder_minutes"]])) { |
|
| 1223 | - $occ = false; |
|
| 1224 | - $occurrences = $this->getItems(time(), 0x7FF00000, 3, true); |
|
| 1225 | - |
|
| 1226 | - for ($i = 0, $len = count($occurrences); $i < $len; ++$i) { |
|
| 1227 | - // This will actually also give us appointments that have already started, but not yet ended. Since we want the next |
|
| 1228 | - // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum |
|
| 1229 | - // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case: |
|
| 1230 | - // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item |
|
| 1231 | - // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped. |
|
| 1232 | - |
|
| 1233 | - if (($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) { |
|
| 1234 | - $occ = $occurrences[$i]; |
|
| 1235 | - |
|
| 1236 | - break; |
|
| 1237 | - } |
|
| 1238 | - } |
|
| 1239 | - |
|
| 1240 | - if ($occ) { |
|
| 1241 | - $propsToSet[$this->proptags["flagdueby"]] = $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60); |
|
| 1242 | - } |
|
| 1243 | - else { |
|
| 1244 | - // Last reminder passed, no reminders any more. |
|
| 1245 | - $propsToSet[$this->proptags["reminder"]] = false; |
|
| 1246 | - $propsToSet[$this->proptags["flagdueby"]] = 0x7FF00000; |
|
| 1247 | - } |
|
| 1248 | - } |
|
| 1249 | - |
|
| 1250 | - // Default data |
|
| 1251 | - // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information) |
|
| 1252 | - $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00); |
|
| 1253 | - |
|
| 1254 | - if (isset($this->recur["startocc"], $this->recur["endocc"])) { |
|
| 1255 | - // Set start and endtime in minutes |
|
| 1256 | - $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]); |
|
| 1257 | - } |
|
| 1258 | - |
|
| 1259 | - // Detailed exception data |
|
| 1260 | - |
|
| 1261 | - $changed_items = $this->recur["changed_occurrences"]; |
|
| 1262 | - |
|
| 1263 | - $rdata .= pack("v", count($changed_items)); |
|
| 1264 | - |
|
| 1265 | - foreach ($changed_items as $changed_item) { |
|
| 1266 | - // Set start and end time of exception |
|
| 1267 | - $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); |
|
| 1268 | - $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); |
|
| 1269 | - $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); |
|
| 1270 | - |
|
| 1271 | - // Bitmask |
|
| 1272 | - $bitmask = 0; |
|
| 1273 | - |
|
| 1274 | - // Check for changed strings |
|
| 1275 | - if (isset($changed_item["subject"])) { |
|
| 1276 | - $bitmask |= 1 << 0; |
|
| 1277 | - } |
|
| 1278 | - |
|
| 1279 | - if (isset($changed_item["remind_before"])) { |
|
| 1280 | - $bitmask |= 1 << 2; |
|
| 1281 | - } |
|
| 1282 | - |
|
| 1283 | - if (isset($changed_item["reminder_set"])) { |
|
| 1284 | - $bitmask |= 1 << 3; |
|
| 1285 | - } |
|
| 1286 | - |
|
| 1287 | - if (isset($changed_item["location"])) { |
|
| 1288 | - $bitmask |= 1 << 4; |
|
| 1289 | - } |
|
| 1290 | - |
|
| 1291 | - if (isset($changed_item["busystatus"])) { |
|
| 1292 | - $bitmask |= 1 << 5; |
|
| 1293 | - } |
|
| 1294 | - |
|
| 1295 | - if (isset($changed_item["alldayevent"])) { |
|
| 1296 | - $bitmask |= 1 << 7; |
|
| 1297 | - } |
|
| 1298 | - |
|
| 1299 | - if (isset($changed_item["label"])) { |
|
| 1300 | - $bitmask |= 1 << 8; |
|
| 1301 | - } |
|
| 1302 | - |
|
| 1303 | - $rdata .= pack("v", $bitmask); |
|
| 1304 | - |
|
| 1305 | - // Set "subject" |
|
| 1306 | - if (isset($changed_item["subject"])) { |
|
| 1307 | - // convert utf-8 to non-unicode blob string (us-ascii?) |
|
| 1308 | - $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]); |
|
| 1309 | - $length = strlen($subject); |
|
| 1310 | - $rdata .= pack("vv", $length + 1, $length); |
|
| 1311 | - $rdata .= pack("a" . $length, $subject); |
|
| 1312 | - } |
|
| 1313 | - |
|
| 1314 | - if (isset($changed_item["remind_before"])) { |
|
| 1315 | - $rdata .= pack("V", $changed_item["remind_before"]); |
|
| 1316 | - } |
|
| 1317 | - |
|
| 1318 | - if (isset($changed_item["reminder_set"])) { |
|
| 1319 | - $rdata .= pack("V", $changed_item["reminder_set"]); |
|
| 1320 | - } |
|
| 1321 | - |
|
| 1322 | - if (isset($changed_item["location"])) { |
|
| 1323 | - $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]); |
|
| 1324 | - $length = strlen($location); |
|
| 1325 | - $rdata .= pack("vv", $length + 1, $length); |
|
| 1326 | - $rdata .= pack("a" . $length, $location); |
|
| 1327 | - } |
|
| 1328 | - |
|
| 1329 | - if (isset($changed_item["busystatus"])) { |
|
| 1330 | - $rdata .= pack("V", $changed_item["busystatus"]); |
|
| 1331 | - } |
|
| 1332 | - |
|
| 1333 | - if (isset($changed_item["alldayevent"])) { |
|
| 1334 | - $rdata .= pack("V", $changed_item["alldayevent"]); |
|
| 1335 | - } |
|
| 1336 | - |
|
| 1337 | - if (isset($changed_item["label"])) { |
|
| 1338 | - $rdata .= pack("V", $changed_item["label"]); |
|
| 1339 | - } |
|
| 1340 | - } |
|
| 1341 | - |
|
| 1342 | - $rdata .= pack("V", 0); |
|
| 1343 | - |
|
| 1344 | - // write extended data |
|
| 1345 | - foreach ($changed_items as $changed_item) { |
|
| 1346 | - $rdata .= pack("V", 0); |
|
| 1347 | - if (isset($changed_item["subject"]) || isset($changed_item["location"])) { |
|
| 1348 | - $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); |
|
| 1349 | - $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); |
|
| 1350 | - $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); |
|
| 1351 | - } |
|
| 1352 | - |
|
| 1353 | - if (isset($changed_item["subject"])) { |
|
| 1354 | - $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]); |
|
| 1355 | - $length = iconv_strlen($subject, "UCS-2LE"); |
|
| 1356 | - $rdata .= pack("v", $length); |
|
| 1357 | - $rdata .= pack("a" . $length * 2, $subject); |
|
| 1358 | - } |
|
| 1359 | - |
|
| 1360 | - if (isset($changed_item["location"])) { |
|
| 1361 | - $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]); |
|
| 1362 | - $length = iconv_strlen($location, "UCS-2LE"); |
|
| 1363 | - $rdata .= pack("v", $length); |
|
| 1364 | - $rdata .= pack("a" . $length * 2, $location); |
|
| 1365 | - } |
|
| 1366 | - |
|
| 1367 | - if (isset($changed_item["subject"]) || isset($changed_item["location"])) { |
|
| 1368 | - $rdata .= pack("V", 0); |
|
| 1369 | - } |
|
| 1370 | - } |
|
| 1371 | - |
|
| 1372 | - $rdata .= pack("V", 0); |
|
| 1373 | - |
|
| 1374 | - // Set props |
|
| 1375 | - $propsToSet[$this->proptags["recurring_data"]] = $rdata; |
|
| 1376 | - $propsToSet[$this->proptags["recurring"]] = true; |
|
| 1377 | - |
|
| 1378 | - if (isset($this->tz) && $this->tz) { |
|
| 1379 | - $timezone = "GMT"; |
|
| 1380 | - if ($this->tz["timezone"] != 0) { |
|
| 1381 | - // Create user readable timezone information |
|
| 1382 | - $timezone = sprintf( |
|
| 1383 | - "(GMT %s%02d:%02d)", |
|
| 1384 | - (-$this->tz["timezone"] > 0 ? "+" : "-"), |
|
| 1385 | - abs($this->tz["timezone"] / 60), |
|
| 1386 | - abs($this->tz["timezone"] % 60) |
|
| 1387 | - ); |
|
| 1388 | - } |
|
| 1389 | - $propsToSet[$this->proptags["timezone_data"]] = $this->getTimezoneData($this->tz); |
|
| 1390 | - $propsToSet[$this->proptags["timezone"]] = $timezone; |
|
| 1391 | - } |
|
| 1392 | - mapi_setprops($this->message, $propsToSet); |
|
| 1393 | - } |
|
| 1394 | - |
|
| 1395 | - /** |
|
| 1396 | - * Function which converts a recurrence date timestamp to an unix date timestamp. |
|
| 1397 | - * |
|
| 1398 | - * @author Steve Hardy |
|
| 1399 | - * |
|
| 1400 | - * @param int $rdate the date which will be converted |
|
| 1401 | - * |
|
| 1402 | - * @return int the converted date |
|
| 1403 | - */ |
|
| 1404 | - public function recurDataToUnixData($rdate) { |
|
| 1405 | - return ($rdate - 194074560) * 60; |
|
| 1406 | - } |
|
| 1407 | - |
|
| 1408 | - /** |
|
| 1409 | - * Function which converts an unix date timestamp to recurrence date timestamp. |
|
| 1410 | - * |
|
| 1411 | - * @author Johnny Biemans |
|
| 1412 | - * |
|
| 1413 | - * @param Date $date the date which will be converted |
|
| 1414 | - * |
|
| 1415 | - * @return int the converted date in minutes |
|
| 1416 | - */ |
|
| 1417 | - public function unixDataToRecurData($date) { |
|
| 1418 | - return ($date / 60) + 194074560; |
|
| 1419 | - } |
|
| 1420 | - |
|
| 1421 | - /** |
|
| 1422 | - * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves. |
|
| 1423 | - * |
|
| 1424 | - * @author Steve Hardy |
|
| 1425 | - * |
|
| 1426 | - * @param mixed $ts |
|
| 1427 | - */ |
|
| 1428 | - public function GetTZOffset($ts) { |
|
| 1429 | - $Offset = date("O", $ts); |
|
| 1430 | - |
|
| 1431 | - $Parity = $Offset < 0 ? -1 : 1; |
|
| 1432 | - $Offset = $Parity * $Offset; |
|
| 1433 | - $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100; |
|
| 1434 | - |
|
| 1435 | - return $Parity * $Offset; |
|
| 1436 | - } |
|
| 1437 | - |
|
| 1438 | - /** |
|
| 1439 | - * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves. |
|
| 1440 | - * |
|
| 1441 | - * @author Steve Hardy |
|
| 1442 | - * |
|
| 1443 | - * @param Date $time |
|
| 1444 | - * |
|
| 1445 | - * @return Date GMT Time |
|
| 1446 | - */ |
|
| 1447 | - public function gmtime($time) { |
|
| 1448 | - $TZOffset = $this->GetTZOffset($time); |
|
| 1449 | - |
|
| 1450 | - $t_time = $time - $TZOffset * 60; # Counter adjust for localtime() |
|
| 1451 | - |
|
| 1452 | - return localtime($t_time, 1); |
|
| 1453 | - } |
|
| 1454 | - |
|
| 1455 | - public function isLeapYear($year) { |
|
| 1456 | - return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0); |
|
| 1457 | - } |
|
| 1458 | - |
|
| 1459 | - public function getMonthInSeconds($year, $month) { |
|
| 1460 | - if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) { |
|
| 1461 | - $day = 31; |
|
| 1462 | - } |
|
| 1463 | - elseif (in_array($month, [4, 6, 9, 11])) { |
|
| 1464 | - $day = 30; |
|
| 1465 | - } |
|
| 1466 | - else { |
|
| 1467 | - $day = 28; |
|
| 1468 | - if ($this->isLeapYear($year) == 1) { |
|
| 1469 | - ++$day; |
|
| 1470 | - } |
|
| 1471 | - } |
|
| 1472 | - |
|
| 1473 | - return $day * 24 * 60 * 60; |
|
| 1474 | - } |
|
| 1475 | - |
|
| 1476 | - /** |
|
| 1477 | - * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour. |
|
| 1478 | - * |
|
| 1479 | - * @param int $year |
|
| 1480 | - * @param int $month |
|
| 1481 | - * @param int $week |
|
| 1482 | - * @param int $day |
|
| 1483 | - * @param int $hour |
|
| 1484 | - * |
|
| 1485 | - * @return returns the timestamp of the given date, timezone-independant |
|
| 1486 | - */ |
|
| 1487 | - public function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) { |
|
| 1488 | - // get first day of month |
|
| 1489 | - $date = gmmktime(0, 0, 0, $month, 0, $year + 1900); |
|
| 1490 | - |
|
| 1491 | - // get wday info |
|
| 1492 | - $gmdate = $this->gmtime($date); |
|
| 1493 | - |
|
| 1494 | - $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week |
|
| 1495 | - |
|
| 1496 | - $date += $week * 7 * 24 * 60 * 60; // go to correct week nr |
|
| 1497 | - $date += $day * 24 * 60 * 60; |
|
| 1498 | - $date += $hour * 60 * 60; |
|
| 1499 | - |
|
| 1500 | - $gmdate = $this->gmtime($date); |
|
| 1501 | - |
|
| 1502 | - // if we are in the next month, then back up a week, because week '5' means |
|
| 1503 | - // 'last week of month' |
|
| 1504 | - |
|
| 1505 | - if ($month != $gmdate["tm_mon"] + 1) { |
|
| 1506 | - $date -= 7 * 24 * 60 * 60; |
|
| 1507 | - } |
|
| 1508 | - |
|
| 1509 | - return $date; |
|
| 1510 | - } |
|
| 1511 | - |
|
| 1512 | - /** |
|
| 1513 | - * getTimezone gives the timezone offset (in minutes) of the given |
|
| 1514 | - * local date/time according to the given TZ info. |
|
| 1515 | - * |
|
| 1516 | - * @param mixed $tz |
|
| 1517 | - * @param mixed $date |
|
| 1518 | - */ |
|
| 1519 | - public function getTimezone($tz, $date) { |
|
| 1520 | - // No timezone -> GMT (+0) |
|
| 1521 | - if (!isset($tz["timezone"])) { |
|
| 1522 | - return 0; |
|
| 1523 | - } |
|
| 1524 | - |
|
| 1525 | - $dst = false; |
|
| 1526 | - $gmdate = $this->gmtime($date); |
|
| 1527 | - |
|
| 1528 | - $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]); |
|
| 1529 | - $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]); |
|
| 1530 | - |
|
| 1531 | - if ($dststart <= $dstend) { |
|
| 1532 | - // Northern hemisphere, eg DST is during Mar-Oct |
|
| 1533 | - if ($date > $dststart && $date < $dstend) { |
|
| 1534 | - $dst = true; |
|
| 1535 | - } |
|
| 1536 | - } |
|
| 1537 | - else { |
|
| 1538 | - // Southern hemisphere, eg DST is during Oct-Mar |
|
| 1539 | - if ($date < $dstend || $date > $dststart) { |
|
| 1540 | - $dst = true; |
|
| 1541 | - } |
|
| 1542 | - } |
|
| 1543 | - |
|
| 1544 | - if ($dst) { |
|
| 1545 | - return $tz["timezone"] + $tz["timezonedst"]; |
|
| 1546 | - } |
|
| 1547 | - |
|
| 1548 | - return $tz["timezone"]; |
|
| 1549 | - } |
|
| 1550 | - |
|
| 1551 | - /** |
|
| 1552 | - * getWeekNr() returns the week nr of the month (ie first week of february is 1). |
|
| 1553 | - * |
|
| 1554 | - * @param mixed $date |
|
| 1555 | - */ |
|
| 1556 | - public function getWeekNr($date) { |
|
| 1557 | - $gmdate = gmtime($date); |
|
| 1558 | - $gmdate["tm_mday"] = 0; |
|
| 1559 | - |
|
| 1560 | - return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1; |
|
| 1561 | - } |
|
| 1562 | - |
|
| 1563 | - /** |
|
| 1564 | - * parseTimezone parses the timezone as specified in named property 0x8233 |
|
| 1565 | - * in Outlook calendar messages. Returns the timezone in minutes negative |
|
| 1566 | - * offset (GMT +2:00 -> -120). |
|
| 1567 | - * |
|
| 1568 | - * @param mixed $data |
|
| 1569 | - */ |
|
| 1570 | - public function parseTimezone($data) { |
|
| 1571 | - if (strlen($data) < 48) { |
|
| 1572 | - return; |
|
| 1573 | - } |
|
| 1574 | - |
|
| 1575 | - return unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data); |
|
| 1576 | - } |
|
| 1577 | - |
|
| 1578 | - public function getTimezoneData($tz) { |
|
| 1579 | - return pack("lllllvvllvlvvlv", $tz["timezone"], 0, $tz["timezonedst"], 0, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendhour"], 0, 0, 0, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststarthour"], 0, 0); |
|
| 1580 | - } |
|
| 1581 | - |
|
| 1582 | - /** |
|
| 1583 | - * createTimezone creates the timezone as specified in the named property 0x8233 |
|
| 1584 | - * see also parseTimezone() |
|
| 1585 | - * $tz is an array with the timezone data. |
|
| 1586 | - * |
|
| 1587 | - * @param mixed $tz |
|
| 1588 | - */ |
|
| 1589 | - public function createTimezone($tz) { |
|
| 1590 | - return pack( |
|
| 1591 | - "lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx", |
|
| 1592 | - $tz["timezone"], |
|
| 1593 | - array_key_exists("timezonedst", $tz) ? $tz["timezonedst"] : 0, |
|
| 1594 | - array_key_exists("dstendmonth", $tz) ? $tz["dstendmonth"] : 0, |
|
| 1595 | - array_key_exists("dstendweek", $tz) ? $tz["dstendweek"] : 0, |
|
| 1596 | - array_key_exists("dstendhour", $tz) ? $tz["dstendhour"] : 0, |
|
| 1597 | - array_key_exists("dststartmonth", $tz) ? $tz["dststartmonth"] : 0, |
|
| 1598 | - array_key_exists("dststartweek", $tz) ? $tz["dststartweek"] : 0, |
|
| 1599 | - array_key_exists("dststarthour", $tz) ? $tz["dststarthour"] : 0 |
|
| 1600 | - ); |
|
| 1601 | - } |
|
| 1602 | - |
|
| 1603 | - /** |
|
| 1604 | - * toGMT returns a timestamp in GMT time for the time and timezone given. |
|
| 1605 | - * |
|
| 1606 | - * @param mixed $tz |
|
| 1607 | - * @param mixed $date |
|
| 1608 | - */ |
|
| 1609 | - public function toGMT($tz, $date) { |
|
| 1610 | - if (!isset($tz['timezone'])) { |
|
| 1611 | - return $date; |
|
| 1612 | - } |
|
| 1613 | - $offset = $this->getTimezone($tz, $date); |
|
| 1614 | - |
|
| 1615 | - return $date + $offset * 60; |
|
| 1616 | - } |
|
| 1617 | - |
|
| 1618 | - /** |
|
| 1619 | - * fromGMT returns a timestamp in the local timezone given from the GMT time given. |
|
| 1620 | - * |
|
| 1621 | - * @param mixed $tz |
|
| 1622 | - * @param mixed $date |
|
| 1623 | - */ |
|
| 1624 | - public function fromGMT($tz, $date) { |
|
| 1625 | - $offset = $this->getTimezone($tz, $date); |
|
| 1626 | - |
|
| 1627 | - return $date - $offset * 60; |
|
| 1628 | - } |
|
| 1629 | - |
|
| 1630 | - /** |
|
| 1631 | - * Function to get timestamp of the beginning of the day of the timestamp given. |
|
| 1632 | - * |
|
| 1633 | - * @param date $date |
|
| 1634 | - * |
|
| 1635 | - * @return date timestamp referring to same day but at 00:00:00 |
|
| 1636 | - */ |
|
| 1637 | - public function dayStartOf($date) { |
|
| 1638 | - $time1 = $this->gmtime($date); |
|
| 1639 | - |
|
| 1640 | - return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900); |
|
| 1641 | - } |
|
| 1642 | - |
|
| 1643 | - /** |
|
| 1644 | - * Function to get timestamp of the beginning of the month of the timestamp given. |
|
| 1645 | - * |
|
| 1646 | - * @param date $date |
|
| 1647 | - * |
|
| 1648 | - * @return date Timestamp referring to same month but on the first day, and at 00:00:00 |
|
| 1649 | - */ |
|
| 1650 | - public function monthStartOf($date) { |
|
| 1651 | - $time1 = $this->gmtime($date); |
|
| 1652 | - |
|
| 1653 | - return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900); |
|
| 1654 | - } |
|
| 1655 | - |
|
| 1656 | - /** |
|
| 1657 | - * Function to get timestamp of the beginning of the year of the timestamp given. |
|
| 1658 | - * |
|
| 1659 | - * @param date $date |
|
| 1660 | - * |
|
| 1661 | - * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00 |
|
| 1662 | - */ |
|
| 1663 | - public function yearStartOf($date) { |
|
| 1664 | - $time1 = $this->gmtime($date); |
|
| 1665 | - |
|
| 1666 | - return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900); |
|
| 1667 | - } |
|
| 1668 | - |
|
| 1669 | - /** |
|
| 1670 | - * Function which returns the items in a given interval. This included expansion of the recurrence and |
|
| 1671 | - * processing of exceptions (modified and deleted). |
|
| 1672 | - * |
|
| 1673 | - * @param string $entryid the entryid of the message |
|
| 1674 | - * @param array $props the properties of the message |
|
| 1675 | - * @param date $start start time of the interval (GMT) |
|
| 1676 | - * @param date $end end time of the interval (GMT) |
|
| 1677 | - * @param mixed $limit |
|
| 1678 | - * @param mixed $remindersonly |
|
| 1679 | - */ |
|
| 1680 | - public function getItems($start, $end, $limit = 0, $remindersonly = false) { |
|
| 1681 | - $items = []; |
|
| 1682 | - |
|
| 1683 | - if (isset($this->recur)) { |
|
| 1684 | - // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which |
|
| 1685 | - // exceptions are in range and have a reminder set |
|
| 1686 | - if ($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) { |
|
| 1687 | - // Sort exceptions by start time |
|
| 1688 | - uasort($this->recur["changed_occurrences"], [$this, "sortExceptionStart"]); |
|
| 1689 | - |
|
| 1690 | - // Loop through all changed exceptions |
|
| 1691 | - foreach ($this->recur["changed_occurrences"] as $exception) { |
|
| 1692 | - // Check reminder set |
|
| 1693 | - if (!isset($exception["reminder"]) || $exception["reminder"] == false) { |
|
| 1694 | - continue; |
|
| 1695 | - } |
|
| 1696 | - |
|
| 1697 | - // Convert to GMT |
|
| 1698 | - $occstart = $this->toGMT($this->tz, $exception["start"]); // seb changed $tz to $this->tz |
|
| 1699 | - $occend = $this->toGMT($this->tz, $exception["end"]); // seb changed $tz to $this->tz |
|
| 1700 | - |
|
| 1701 | - // Check range criterium |
|
| 1702 | - if ($occstart > $end || $occend < $start) { |
|
| 1703 | - continue; |
|
| 1704 | - } |
|
| 1705 | - |
|
| 1706 | - // OK, add to items. |
|
| 1707 | - array_push($items, $this->getExceptionProperties($exception)); |
|
| 1708 | - if ($limit && (count($items) == $limit)) { |
|
| 1709 | - break; |
|
| 1710 | - } |
|
| 1711 | - } |
|
| 1712 | - |
|
| 1713 | - uasort($items, [$this, "sortStarttime"]); |
|
| 1714 | - |
|
| 1715 | - return $items; |
|
| 1716 | - } |
|
| 1717 | - |
|
| 1718 | - // From here on, the dates of the occurrences are calculated in local time, so the days we're looking |
|
| 1719 | - // at are calculated from the local time dates of $start and $end |
|
| 1720 | - // TODO use one isset |
|
| 1721 | - if (isset($this->recur['regen'], $this->action['datecompleted']) && $this->recur['regen']) { |
|
| 1722 | - $daystart = $this->dayStartOf($this->action['datecompleted']); |
|
| 1723 | - } |
|
| 1724 | - else { |
|
| 1725 | - $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence |
|
| 1726 | - } |
|
| 1727 | - |
|
| 1728 | - // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view |
|
| 1729 | - // or the end of the recurrence, whichever comes first |
|
| 1730 | - if ($end > $this->toGMT($this->tz, $this->recur["end"])) { |
|
| 1731 | - $rangeend = $this->toGMT($this->tz, $this->recur["end"]); |
|
| 1732 | - } |
|
| 1733 | - else { |
|
| 1734 | - $rangeend = $end; |
|
| 1735 | - } |
|
| 1736 | - |
|
| 1737 | - $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend)); |
|
| 1738 | - |
|
| 1739 | - // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range. |
|
| 1740 | - |
|
| 1741 | - switch ($this->recur["type"]) { |
|
| 1742 | - case 10: |
|
| 1743 | - // Daily |
|
| 1744 | - if ($this->recur["everyn"] <= 0) { |
|
| 1745 | - $this->recur["everyn"] = 1440; |
|
| 1746 | - } |
|
| 1747 | - |
|
| 1748 | - if ($this->recur["subtype"] == 0) { |
|
| 1749 | - // Every Nth day |
|
| 1750 | - for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) { |
|
| 1751 | - $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1752 | - } |
|
| 1753 | - } |
|
| 1754 | - else { |
|
| 1755 | - // Every workday |
|
| 1756 | - for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) { |
|
| 1757 | - $nowtime = $this->gmtime($now); |
|
| 1758 | - if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace |
|
| 1759 | - $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1760 | - } |
|
| 1761 | - } |
|
| 1762 | - } |
|
| 1763 | - |
|
| 1764 | - break; |
|
| 1765 | - |
|
| 1766 | - case 11: |
|
| 1767 | - // Weekly |
|
| 1768 | - if ($this->recur["everyn"] <= 0) { |
|
| 1769 | - $this->recur["everyn"] = 1; |
|
| 1770 | - } |
|
| 1771 | - |
|
| 1772 | - // If sliding flag is set then move to 'n' weeks |
|
| 1773 | - if ($this->recur['regen']) { |
|
| 1774 | - $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]); |
|
| 1775 | - } |
|
| 1776 | - |
|
| 1777 | - for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) { |
|
| 1778 | - if ($this->recur['regen']) { |
|
| 1779 | - $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1780 | - } |
|
| 1781 | - else { |
|
| 1782 | - // Loop through the whole following week to the first occurrence of the week, add each day that is specified |
|
| 1783 | - for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1784 | - $daynow = $now + $wday * 60 * 60 * 24; |
|
| 1785 | - // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item |
|
| 1786 | - if ($daynow <= $dayend) { |
|
| 1787 | - $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1788 | - if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ? |
|
| 1789 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1790 | - } |
|
| 1791 | - } |
|
| 1792 | - } |
|
| 1793 | - } |
|
| 1794 | - } |
|
| 1795 | - |
|
| 1796 | - break; |
|
| 1797 | - |
|
| 1798 | - case 12: |
|
| 1799 | - // Monthly |
|
| 1800 | - if ($this->recur["everyn"] <= 0) { |
|
| 1801 | - $this->recur["everyn"] = 1; |
|
| 1802 | - } |
|
| 1803 | - |
|
| 1804 | - // Loop through all months from start to end of occurrence, starting at beginning of first month |
|
| 1805 | - for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1806 | - if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months |
|
| 1807 | - $difference = 1; |
|
| 1808 | - if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) { |
|
| 1809 | - $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1; |
|
| 1810 | - } |
|
| 1811 | - $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60); |
|
| 1812 | - // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item |
|
| 1813 | - if ($daynow <= $dayend) { |
|
| 1814 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1815 | - } |
|
| 1816 | - } |
|
| 1817 | - elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months |
|
| 1818 | - // Sanitize input |
|
| 1819 | - if ($this->recur["weekdays"] == 0) { |
|
| 1820 | - $this->recur["weekdays"] = 1; |
|
| 1821 | - } |
|
| 1822 | - |
|
| 1823 | - // If nday is not set to the last day in the month |
|
| 1824 | - if ($this->recur["nday"] < 5) { |
|
| 1825 | - // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched |
|
| 1826 | - $ndaycounter = 0; |
|
| 1827 | - // Find matching weekday in this month |
|
| 1828 | - for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) { |
|
| 1829 | - $daynow = $now + $day * 60 * 60 * 24; |
|
| 1830 | - $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1831 | - |
|
| 1832 | - if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1833 | - ++$ndaycounter; |
|
| 1834 | - } |
|
| 1835 | - // check the selected pattern is same as asked Nth weekday,If so set the firstday |
|
| 1836 | - if ($this->recur["nday"] == $ndaycounter) { |
|
| 1837 | - $firstday = $day; |
|
| 1838 | - |
|
| 1839 | - break; |
|
| 1840 | - } |
|
| 1841 | - } |
|
| 1842 | - // $firstday is the day of the month on which the asked pattern of nth weekday matches |
|
| 1843 | - $daynow = $now + $firstday * 60 * 60 * 24; |
|
| 1844 | - } |
|
| 1845 | - else { |
|
| 1846 | - // Find last day in the month ($now is the firstday of the month) |
|
| 1847 | - $NumDaysInMonth = $this->daysInMonth($now, 1); |
|
| 1848 | - $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60); |
|
| 1849 | - |
|
| 1850 | - $nowtime = $this->gmtime($daynow); |
|
| 1851 | - while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) { |
|
| 1852 | - $daynow -= 86400; |
|
| 1853 | - $nowtime = $this->gmtime($daynow); |
|
| 1854 | - } |
|
| 1855 | - } |
|
| 1856 | - |
|
| 1857 | - /* |
|
| 8 | + /** |
|
| 9 | + * BaseRecurrence |
|
| 10 | + * this class is superclass for recurrence for appointments and tasks. This class provides all |
|
| 11 | + * basic features of recurrence. |
|
| 12 | + */ |
|
| 13 | + class BaseRecurrence { |
|
| 14 | + /** |
|
| 15 | + * @var object Mapi Message Store (may be null if readonly) |
|
| 16 | + */ |
|
| 17 | + public $store; |
|
| 18 | + |
|
| 19 | + /** |
|
| 20 | + * @var object Mapi Message (may be null if readonly) |
|
| 21 | + */ |
|
| 22 | + public $message; |
|
| 23 | + |
|
| 24 | + /** |
|
| 25 | + * @var array Message Properties |
|
| 26 | + */ |
|
| 27 | + public $messageprops; |
|
| 28 | + |
|
| 29 | + /** |
|
| 30 | + * @var array list of property tags |
|
| 31 | + */ |
|
| 32 | + public $proptags; |
|
| 33 | + |
|
| 34 | + /** |
|
| 35 | + * @var recurrence data of this calendar item |
|
| 36 | + */ |
|
| 37 | + public $recur; |
|
| 38 | + |
|
| 39 | + /** |
|
| 40 | + * @var Timezone data of this calendar item |
|
| 41 | + */ |
|
| 42 | + public $tz; |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * Constructor. |
|
| 46 | + * |
|
| 47 | + * @param resource $store MAPI Message Store Object |
|
| 48 | + * @param resource $message the MAPI (appointment) message |
|
| 49 | + * @param array $properties the list of MAPI properties the message has |
|
| 50 | + */ |
|
| 51 | + public function __construct($store, $message) { |
|
| 52 | + $this->store = $store; |
|
| 53 | + |
|
| 54 | + if (is_array($message)) { |
|
| 55 | + $this->messageprops = $message; |
|
| 56 | + } |
|
| 57 | + else { |
|
| 58 | + $this->message = $message; |
|
| 59 | + $this->messageprops = mapi_getprops($this->message, $this->proptags); |
|
| 60 | + } |
|
| 61 | + |
|
| 62 | + if (isset($this->messageprops[$this->proptags["recurring_data"]])) { |
|
| 63 | + // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface |
|
| 64 | + if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) { |
|
| 65 | + $this->getFullRecurrenceBlob(); |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]); |
|
| 69 | + } |
|
| 70 | + if (isset($this->proptags["timezone_data"], $this->messageprops[$this->proptags["timezone_data"]])) { |
|
| 71 | + $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]); |
|
| 72 | + } |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + public function getRecurrence() { |
|
| 76 | + return $this->recur; |
|
| 77 | + } |
|
| 78 | + |
|
| 79 | + public function getFullRecurrenceBlob() { |
|
| 80 | + $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]); |
|
| 81 | + |
|
| 82 | + $recurrBlob = ''; |
|
| 83 | + $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0); |
|
| 84 | + $stat = mapi_stream_stat($stream); |
|
| 85 | + |
|
| 86 | + for ($i = 0; $i < $stat['cb']; $i += 1024) { |
|
| 87 | + $recurrBlob .= mapi_stream_read($stream, 1024); |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + if (!empty($recurrBlob)) { |
|
| 91 | + $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob; |
|
| 92 | + } |
|
| 93 | + } |
|
| 94 | + |
|
| 95 | + /** |
|
| 96 | + * Function for parsing the Recurrence value of a Calendar item. |
|
| 97 | + * |
|
| 98 | + * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the |
|
| 99 | + * data to this function |
|
| 100 | + * |
|
| 101 | + * Returns a structure containing the data: |
|
| 102 | + * |
|
| 103 | + * type - type of recurrence: day=10, week=11, month=12, year=13 |
|
| 104 | + * subtype - type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday) |
|
| 105 | + * start - unix timestamp of first occurrence |
|
| 106 | + * end - unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1 |
|
| 107 | + * numoccur - occurrences (may be very large when there is no end data) |
|
| 108 | + * |
|
| 109 | + * then, for each type: |
|
| 110 | + * |
|
| 111 | + * Daily: |
|
| 112 | + * everyn - every [everyn] days in minutes |
|
| 113 | + * regen - regenerating event (like tasks) |
|
| 114 | + * |
|
| 115 | + * Weekly: |
|
| 116 | + * everyn - every [everyn] weeks in weeks |
|
| 117 | + * regen - regenerating event (like tasks) |
|
| 118 | + * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
|
| 119 | + * |
|
| 120 | + * Monthly: |
|
| 121 | + * everyn - every [everyn] months |
|
| 122 | + * regen - regenerating event (like tasks) |
|
| 123 | + * |
|
| 124 | + * subtype 2: |
|
| 125 | + * monthday - on day [monthday] of the month |
|
| 126 | + * |
|
| 127 | + * subtype 3: |
|
| 128 | + * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
|
| 129 | + * nday - on [nday]'th [weekdays] of the month |
|
| 130 | + * |
|
| 131 | + * Yearly: |
|
| 132 | + * everyn - every [everyn] months (12, 24, 36, ...) |
|
| 133 | + * month - in month [month] (although the month is encoded in minutes since the startning of the year ........) |
|
| 134 | + * regen - regenerating event (like tasks) |
|
| 135 | + * |
|
| 136 | + * subtype 2: |
|
| 137 | + * monthday - on day [monthday] of the month |
|
| 138 | + * |
|
| 139 | + * subtype 3: |
|
| 140 | + * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
|
| 141 | + * nday - on [nday]'th [weekdays] of the month [month] |
|
| 142 | + * |
|
| 143 | + * @param string $rdata Binary string |
|
| 144 | + * |
|
| 145 | + * @return array recurrence data |
|
| 146 | + */ |
|
| 147 | + public function parseRecurrence($rdata) { |
|
| 148 | + if (strlen($rdata) < 10) { |
|
| 149 | + return; |
|
| 150 | + } |
|
| 151 | + |
|
| 152 | + $ret["changed_occurrences"] = []; |
|
| 153 | + $ret["deleted_occurrences"] = []; |
|
| 154 | + |
|
| 155 | + $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata); |
|
| 156 | + |
|
| 157 | + $ret["type"] = $data["rtype"]; |
|
| 158 | + $ret["subtype"] = $data["rtype2"]; |
|
| 159 | + $rdata = substr($rdata, 10); |
|
| 160 | + |
|
| 161 | + switch ($data["rtype"]) { |
|
| 162 | + case 0x0A: |
|
| 163 | + // Daily |
|
| 164 | + if (strlen($rdata) < 12) { |
|
| 165 | + return $ret; |
|
| 166 | + } |
|
| 167 | + |
|
| 168 | + $data = unpack("Vunknown/Veveryn/Vregen", $rdata); |
|
| 169 | + $ret["everyn"] = $data["everyn"]; |
|
| 170 | + $ret["regen"] = $data["regen"]; |
|
| 171 | + |
|
| 172 | + switch ($ret["subtype"]) { |
|
| 173 | + case 0: |
|
| 174 | + $rdata = substr($rdata, 12); |
|
| 175 | + |
|
| 176 | + break; |
|
| 177 | + |
|
| 178 | + case 1: |
|
| 179 | + $rdata = substr($rdata, 16); |
|
| 180 | + |
|
| 181 | + break; |
|
| 182 | + } |
|
| 183 | + |
|
| 184 | + break; |
|
| 185 | + |
|
| 186 | + case 0x0B: |
|
| 187 | + // Weekly |
|
| 188 | + if (strlen($rdata) < 16) { |
|
| 189 | + return $ret; |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + $data = unpack("Vconst1/Veveryn/Vregen", $rdata); |
|
| 193 | + $rdata = substr($rdata, 12); |
|
| 194 | + |
|
| 195 | + $ret["everyn"] = $data["everyn"]; |
|
| 196 | + $ret["regen"] = $data["regen"]; |
|
| 197 | + $ret["weekdays"] = 0; |
|
| 198 | + |
|
| 199 | + if ($data["regen"] == 0) { |
|
| 200 | + $data = unpack("Vweekdays", $rdata); |
|
| 201 | + $rdata = substr($rdata, 4); |
|
| 202 | + |
|
| 203 | + $ret["weekdays"] = $data["weekdays"]; |
|
| 204 | + } |
|
| 205 | + |
|
| 206 | + break; |
|
| 207 | + |
|
| 208 | + case 0x0C: |
|
| 209 | + // Monthly |
|
| 210 | + if (strlen($rdata) < 16) { |
|
| 211 | + return $ret; |
|
| 212 | + } |
|
| 213 | + |
|
| 214 | + $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata); |
|
| 215 | + |
|
| 216 | + $ret["everyn"] = $data["everyn"]; |
|
| 217 | + $ret["regen"] = $data["regen"]; |
|
| 218 | + |
|
| 219 | + if ($ret["subtype"] == 3) { |
|
| 220 | + $ret["weekdays"] = $data["monthday"]; |
|
| 221 | + } |
|
| 222 | + else { |
|
| 223 | + $ret["monthday"] = $data["monthday"]; |
|
| 224 | + } |
|
| 225 | + |
|
| 226 | + $rdata = substr($rdata, 16); |
|
| 227 | + |
|
| 228 | + if ($ret["subtype"] == 3) { |
|
| 229 | + $data = unpack("Vnday", $rdata); |
|
| 230 | + $ret["nday"] = $data["nday"]; |
|
| 231 | + $rdata = substr($rdata, 4); |
|
| 232 | + } |
|
| 233 | + |
|
| 234 | + break; |
|
| 235 | + |
|
| 236 | + case 0x0D: |
|
| 237 | + // Yearly |
|
| 238 | + if (strlen($rdata) < 16) { |
|
| 239 | + return $ret; |
|
| 240 | + } |
|
| 241 | + |
|
| 242 | + $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata); |
|
| 243 | + |
|
| 244 | + $ret["month"] = $data["month"]; |
|
| 245 | + $ret["everyn"] = $data["everyn"]; |
|
| 246 | + $ret["regen"] = $data["regen"]; |
|
| 247 | + |
|
| 248 | + if ($ret["subtype"] == 3) { |
|
| 249 | + $ret["weekdays"] = $data["monthday"]; |
|
| 250 | + } |
|
| 251 | + else { |
|
| 252 | + $ret["monthday"] = $data["monthday"]; |
|
| 253 | + } |
|
| 254 | + |
|
| 255 | + $rdata = substr($rdata, 16); |
|
| 256 | + |
|
| 257 | + if ($ret["subtype"] == 3) { |
|
| 258 | + $data = unpack("Vnday", $rdata); |
|
| 259 | + $ret["nday"] = $data["nday"]; |
|
| 260 | + $rdata = substr($rdata, 4); |
|
| 261 | + } |
|
| 262 | + |
|
| 263 | + break; |
|
| 264 | + } |
|
| 265 | + |
|
| 266 | + if (strlen($rdata) < 16) { |
|
| 267 | + return $ret; |
|
| 268 | + } |
|
| 269 | + |
|
| 270 | + $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata); |
|
| 271 | + |
|
| 272 | + $rdata = substr($rdata, 16); |
|
| 273 | + |
|
| 274 | + $ret["term"] = $data["term"]; |
|
| 275 | + $ret["numoccur"] = $data["numoccur"]; |
|
| 276 | + $ret["numexcept"] = $data["numexcept"]; |
|
| 277 | + |
|
| 278 | + // exc_base_dates are *all* the base dates that have been either deleted or modified |
|
| 279 | + $exc_base_dates = []; |
|
| 280 | + for ($i = 0; $i < $ret["numexcept"]; ++$i) { |
|
| 281 | + if (strlen($rdata) < 4) { |
|
| 282 | + // We shouldn't arrive here, because that implies |
|
| 283 | + // numexcept does not match the amount of data |
|
| 284 | + // which is available for the exceptions. |
|
| 285 | + return $ret; |
|
| 286 | + } |
|
| 287 | + $data = unpack("Vbasedate", $rdata); |
|
| 288 | + $rdata = substr($rdata, 4); |
|
| 289 | + $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]); |
|
| 290 | + } |
|
| 291 | + |
|
| 292 | + if (strlen($rdata) < 4) { |
|
| 293 | + return $ret; |
|
| 294 | + } |
|
| 295 | + |
|
| 296 | + $data = unpack("Vnumexceptmod", $rdata); |
|
| 297 | + $rdata = substr($rdata, 4); |
|
| 298 | + |
|
| 299 | + $ret["numexceptmod"] = $data["numexceptmod"]; |
|
| 300 | + |
|
| 301 | + // exc_changed are the base dates of *modified* occurrences. exactly what is modified |
|
| 302 | + // is in the attachments *and* in the data further down this function. |
|
| 303 | + $exc_changed = []; |
|
| 304 | + for ($i = 0; $i < $ret["numexceptmod"]; ++$i) { |
|
| 305 | + if (strlen($rdata) < 4) { |
|
| 306 | + // We shouldn't arrive here, because that implies |
|
| 307 | + // numexceptmod does not match the amount of data |
|
| 308 | + // which is available for the exceptions. |
|
| 309 | + return $ret; |
|
| 310 | + } |
|
| 311 | + $data = unpack("Vstartdate", $rdata); |
|
| 312 | + $rdata = substr($rdata, 4); |
|
| 313 | + $exc_changed[] = $this->recurDataToUnixData($data["startdate"]); |
|
| 314 | + } |
|
| 315 | + |
|
| 316 | + if (strlen($rdata) < 8) { |
|
| 317 | + return $ret; |
|
| 318 | + } |
|
| 319 | + |
|
| 320 | + $data = unpack("Vstart/Vend", $rdata); |
|
| 321 | + $rdata = substr($rdata, 8); |
|
| 322 | + |
|
| 323 | + $ret["start"] = $this->recurDataToUnixData($data["start"]); |
|
| 324 | + $ret["end"] = $this->recurDataToUnixData($data["end"]); |
|
| 325 | + |
|
| 326 | + // this is where task recurrence stop |
|
| 327 | + if (strlen($rdata) < 16) { |
|
| 328 | + return $ret; |
|
| 329 | + } |
|
| 330 | + |
|
| 331 | + $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata); |
|
| 332 | + $rdata = substr($rdata, 16); |
|
| 333 | + |
|
| 334 | + $ret["startocc"] = $data["startmin"]; |
|
| 335 | + $ret["endocc"] = $data["endmin"]; |
|
| 336 | + $writerversion = $data["writerversion"]; |
|
| 337 | + |
|
| 338 | + $data = unpack("vnumber", $rdata); |
|
| 339 | + $rdata = substr($rdata, 2); |
|
| 340 | + |
|
| 341 | + $nexceptions = $data["number"]; |
|
| 342 | + $exc_changed_details = []; |
|
| 343 | + |
|
| 344 | + // Parse n modified exceptions |
|
| 345 | + for ($i = 0; $i < $nexceptions; ++$i) { |
|
| 346 | + $item = []; |
|
| 347 | + |
|
| 348 | + // Get exception startdate, enddate and basedate (the date at which the occurrence would have started) |
|
| 349 | + $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata); |
|
| 350 | + $rdata = substr($rdata, 12); |
|
| 351 | + |
|
| 352 | + // Convert recurtimestamp to unix timestamp |
|
| 353 | + $startdate = $this->recurDataToUnixData($data["startdate"]); |
|
| 354 | + $enddate = $this->recurDataToUnixData($data["enddate"]); |
|
| 355 | + $basedate = $this->recurDataToUnixData($data["basedate"]); |
|
| 356 | + |
|
| 357 | + // Set the right properties |
|
| 358 | + $item["basedate"] = $this->dayStartOf($basedate); |
|
| 359 | + $item["start"] = $startdate; |
|
| 360 | + $item["end"] = $enddate; |
|
| 361 | + |
|
| 362 | + $data = unpack("vbitmask", $rdata); |
|
| 363 | + $rdata = substr($rdata, 2); |
|
| 364 | + $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions |
|
| 365 | + |
|
| 366 | + // Bitmask to verify what properties are changed |
|
| 367 | + $bitmask = $data["bitmask"]; |
|
| 368 | + |
|
| 369 | + // ARO_SUBJECT: 0x0001 |
|
| 370 | + // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject |
|
| 371 | + if (($bitmask & (1 << 0))) { |
|
| 372 | + $data = unpack("vnull_length/vlength", $rdata); |
|
| 373 | + $rdata = substr($rdata, 4); |
|
| 374 | + |
|
| 375 | + $length = $data["length"]; |
|
| 376 | + $item["subject"] = ""; // Normalized subject |
|
| 377 | + for ($j = 0; $j < $length && strlen($rdata); ++$j) { |
|
| 378 | + $data = unpack("Cchar", $rdata); |
|
| 379 | + $rdata = substr($rdata, 1); |
|
| 380 | + |
|
| 381 | + $item["subject"] .= chr($data["char"]); |
|
| 382 | + } |
|
| 383 | + } |
|
| 384 | + |
|
| 385 | + // ARO_MEETINGTYPE: 0x0002 |
|
| 386 | + if (($bitmask & (1 << 1))) { |
|
| 387 | + $rdata = substr($rdata, 4); |
|
| 388 | + // Attendees modified: no data here (only in attachment) |
|
| 389 | + } |
|
| 390 | + |
|
| 391 | + // ARO_REMINDERDELTA: 0x0004 |
|
| 392 | + // Look for field: ReminderDelta (4b) |
|
| 393 | + if (($bitmask & (1 << 2))) { |
|
| 394 | + $data = unpack("Vremind_before", $rdata); |
|
| 395 | + $rdata = substr($rdata, 4); |
|
| 396 | + |
|
| 397 | + $item["remind_before"] = $data["remind_before"]; |
|
| 398 | + } |
|
| 399 | + |
|
| 400 | + // ARO_REMINDER: 0x0008 |
|
| 401 | + // Look field: ReminderSet (4b) |
|
| 402 | + if (($bitmask & (1 << 3))) { |
|
| 403 | + $data = unpack("Vreminder_set", $rdata); |
|
| 404 | + $rdata = substr($rdata, 4); |
|
| 405 | + |
|
| 406 | + $item["reminder_set"] = $data["reminder_set"]; |
|
| 407 | + } |
|
| 408 | + |
|
| 409 | + // ARO_LOCATION: 0x0010 |
|
| 410 | + // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location |
|
| 411 | + // Similar to ARO_SUBJECT above. |
|
| 412 | + if (($bitmask & (1 << 4))) { |
|
| 413 | + $data = unpack("vnull_length/vlength", $rdata); |
|
| 414 | + $rdata = substr($rdata, 4); |
|
| 415 | + |
|
| 416 | + $item["location"] = ""; |
|
| 417 | + |
|
| 418 | + $length = $data["length"]; |
|
| 419 | + $data = substr($rdata, 0, $length); |
|
| 420 | + $rdata = substr($rdata, $length); |
|
| 421 | + |
|
| 422 | + $item["location"] .= $data; |
|
| 423 | + } |
|
| 424 | + |
|
| 425 | + // ARO_BUSYSTATUS: 0x0020 |
|
| 426 | + // Look for field: BusyStatus (4b) |
|
| 427 | + if (($bitmask & (1 << 5))) { |
|
| 428 | + $data = unpack("Vbusystatus", $rdata); |
|
| 429 | + $rdata = substr($rdata, 4); |
|
| 430 | + |
|
| 431 | + $item["busystatus"] = $data["busystatus"]; |
|
| 432 | + } |
|
| 433 | + |
|
| 434 | + // ARO_ATTACHMENT: 0x0040 |
|
| 435 | + if (($bitmask & (1 << 6))) { |
|
| 436 | + // no data: RESERVED |
|
| 437 | + $rdata = substr($rdata, 4); |
|
| 438 | + } |
|
| 439 | + |
|
| 440 | + // ARO_SUBTYPE: 0x0080 |
|
| 441 | + // Look for field: SubType (4b). Determines whether it is an allday event. |
|
| 442 | + if (($bitmask & (1 << 7))) { |
|
| 443 | + $data = unpack("Vallday", $rdata); |
|
| 444 | + $rdata = substr($rdata, 4); |
|
| 445 | + |
|
| 446 | + $item["alldayevent"] = $data["allday"]; |
|
| 447 | + } |
|
| 448 | + |
|
| 449 | + // ARO_APPTCOLOR: 0x0100 |
|
| 450 | + // Look for field: AppointmentColor (4b) |
|
| 451 | + if (($bitmask & (1 << 8))) { |
|
| 452 | + $data = unpack("Vlabel", $rdata); |
|
| 453 | + $rdata = substr($rdata, 4); |
|
| 454 | + |
|
| 455 | + $item["label"] = $data["label"]; |
|
| 456 | + } |
|
| 457 | + |
|
| 458 | + // ARO_EXCEPTIONAL_BODY: 0x0200 |
|
| 459 | + if (($bitmask & (1 << 9))) { |
|
| 460 | + // Notes or Attachments modified: no data here (only in attachment) |
|
| 461 | + } |
|
| 462 | + |
|
| 463 | + array_push($exc_changed_details, $item); |
|
| 464 | + } |
|
| 465 | + |
|
| 466 | + /** |
|
| 467 | + * We now have $exc_changed, $exc_base_dates and $exc_changed_details |
|
| 468 | + * We will ignore $exc_changed, as this information is available in $exc_changed_details |
|
| 469 | + * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item |
|
| 470 | + * has been deleted. |
|
| 471 | + */ |
|
| 472 | + |
|
| 473 | + // Find deleted occurrences |
|
| 474 | + $deleted_occurrences = []; |
|
| 475 | + |
|
| 476 | + foreach ($exc_base_dates as $base_date) { |
|
| 477 | + $found = false; |
|
| 478 | + |
|
| 479 | + foreach ($exc_changed_details as $details) { |
|
| 480 | + if ($details["basedate"] == $base_date) { |
|
| 481 | + $found = true; |
|
| 482 | + |
|
| 483 | + break; |
|
| 484 | + } |
|
| 485 | + } |
|
| 486 | + if (!$found) { |
|
| 487 | + // item was not in exc_changed_details, so it must be deleted |
|
| 488 | + $deleted_occurrences[] = $base_date; |
|
| 489 | + } |
|
| 490 | + } |
|
| 491 | + |
|
| 492 | + $ret["deleted_occurrences"] = $deleted_occurrences; |
|
| 493 | + $ret["changed_occurrences"] = $exc_changed_details; |
|
| 494 | + |
|
| 495 | + // enough data for normal exception (no extended data) |
|
| 496 | + if (strlen($rdata) < 16) { |
|
| 497 | + return $ret; |
|
| 498 | + } |
|
| 499 | + |
|
| 500 | + $data = unpack("Vreservedsize", $rdata); |
|
| 501 | + $rdata = substr($rdata, 4 + $data["reservedsize"]); |
|
| 502 | + |
|
| 503 | + for ($i = 0; $i < $nexceptions; ++$i) { |
|
| 504 | + // subject and location in ucs-2 to utf-8 |
|
| 505 | + if ($writerversion >= 0x3009) { |
|
| 506 | + $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4 |
|
| 507 | + $rdata = substr($rdata, 4 + $data["size"]); |
|
| 508 | + } |
|
| 509 | + |
|
| 510 | + $data = unpack("Vreservedsize", $rdata); |
|
| 511 | + $rdata = substr($rdata, 4 + $data["reservedsize"]); |
|
| 512 | + |
|
| 513 | + // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10) |
|
| 514 | + if ($exc_changed_details[$i]["bitmask"] & 0x11) { |
|
| 515 | + $data = unpack("Vstart/Vend/Vorig", $rdata); |
|
| 516 | + $rdata = substr($rdata, 4 * 3); |
|
| 517 | + |
|
| 518 | + $exc_changed_details[$i]["ex_start_datetime"] = $data["start"]; |
|
| 519 | + $exc_changed_details[$i]["ex_end_datetime"] = $data["end"]; |
|
| 520 | + $exc_changed_details[$i]["ex_orig_date"] = $data["orig"]; |
|
| 521 | + } |
|
| 522 | + |
|
| 523 | + // ARO_SUBJECT |
|
| 524 | + if ($exc_changed_details[$i]["bitmask"] & 0x01) { |
|
| 525 | + // decode ucs2 string to utf-8 |
|
| 526 | + $data = unpack("vlength", $rdata); |
|
| 527 | + $rdata = substr($rdata, 2); |
|
| 528 | + $length = $data["length"]; |
|
| 529 | + $data = substr($rdata, 0, $length * 2); |
|
| 530 | + $rdata = substr($rdata, $length * 2); |
|
| 531 | + $subject = iconv("UCS-2LE", "UTF-8", $data); |
|
| 532 | + // replace subject with unicode subject |
|
| 533 | + $exc_changed_details[$i]["subject"] = $subject; |
|
| 534 | + } |
|
| 535 | + |
|
| 536 | + // ARO_LOCATION |
|
| 537 | + if ($exc_changed_details[$i]["bitmask"] & 0x10) { |
|
| 538 | + // decode ucs2 string to utf-8 |
|
| 539 | + $data = unpack("vlength", $rdata); |
|
| 540 | + $rdata = substr($rdata, 2); |
|
| 541 | + $length = $data["length"]; |
|
| 542 | + $data = substr($rdata, 0, $length * 2); |
|
| 543 | + $rdata = substr($rdata, $length * 2); |
|
| 544 | + $location = iconv("UCS-2LE", "UTF-8", $data); |
|
| 545 | + // replace subject with unicode subject |
|
| 546 | + $exc_changed_details[$i]["location"] = $location; |
|
| 547 | + } |
|
| 548 | + |
|
| 549 | + // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10) |
|
| 550 | + if ($exc_changed_details[$i]["bitmask"] & 0x11) { |
|
| 551 | + $data = unpack("Vreservedsize", $rdata); |
|
| 552 | + $rdata = substr($rdata, 4 + $data["reservedsize"]); |
|
| 553 | + } |
|
| 554 | + } |
|
| 555 | + |
|
| 556 | + // update with extended data |
|
| 557 | + $ret["changed_occurrences"] = $exc_changed_details; |
|
| 558 | + |
|
| 559 | + return $ret; |
|
| 560 | + } |
|
| 561 | + |
|
| 562 | + /** |
|
| 563 | + * Saves the recurrence data to the recurrence property. |
|
| 564 | + */ |
|
| 565 | + public function saveRecurrence() { |
|
| 566 | + // Only save if a message was passed |
|
| 567 | + if (!isset($this->message)) { |
|
| 568 | + return; |
|
| 569 | + } |
|
| 570 | + |
|
| 571 | + // Abort if no recurrence was set |
|
| 572 | + if (!isset($this->recur["type"], $this->recur["subtype"], $this->recur["start"], $this->recur["end"], $this->recur["startocc"], $this->recur["endocc"])) { |
|
| 573 | + return; |
|
| 574 | + } |
|
| 575 | + |
|
| 576 | + $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]); |
|
| 577 | + |
|
| 578 | + $weekstart = 1; // monday |
|
| 579 | + $forwardcount = 0; |
|
| 580 | + $restocc = 0; |
|
| 581 | + $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday) |
|
| 582 | + |
|
| 583 | + $term = (int) $this->recur["type"]; |
|
| 584 | + |
|
| 585 | + switch ($term) { |
|
| 586 | + case 0x0A: |
|
| 587 | + // Daily |
|
| 588 | + if (!isset($this->recur["everyn"])) { |
|
| 589 | + return; |
|
| 590 | + } |
|
| 591 | + |
|
| 592 | + if ($this->recur["subtype"] == 1) { |
|
| 593 | + // Daily every workday |
|
| 594 | + $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E); |
|
| 595 | + } |
|
| 596 | + else { |
|
| 597 | + // Daily every N days (everyN in minutes) |
|
| 598 | + |
|
| 599 | + $everyn = ((int) $this->recur["everyn"]) / 1440; |
|
| 600 | + |
|
| 601 | + // Calc first occ |
|
| 602 | + $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]); |
|
| 603 | + |
|
| 604 | + $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0); |
|
| 605 | + } |
|
| 606 | + |
|
| 607 | + break; |
|
| 608 | + |
|
| 609 | + case 0x0B: |
|
| 610 | + // Weekly |
|
| 611 | + if (!isset($this->recur["everyn"])) { |
|
| 612 | + return; |
|
| 613 | + } |
|
| 614 | + |
|
| 615 | + if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) { |
|
| 616 | + return; |
|
| 617 | + } |
|
| 618 | + |
|
| 619 | + // No need to calculate startdate if sliding flag was set. |
|
| 620 | + if (!$this->recur['regen']) { |
|
| 621 | + // Calculate start date of recurrence |
|
| 622 | + |
|
| 623 | + // Find the first day that matches one of the weekdays selected |
|
| 624 | + $daycount = 0; |
|
| 625 | + $dayskip = -1; |
|
| 626 | + for ($j = 0; $j < 7; ++$j) { |
|
| 627 | + if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 628 | + if ($dayskip == -1) { |
|
| 629 | + $dayskip = $j; |
|
| 630 | + } |
|
| 631 | + |
|
| 632 | + ++$daycount; |
|
| 633 | + } |
|
| 634 | + } |
|
| 635 | + |
|
| 636 | + // $dayskip is the number of days to skip from the startdate until the first occurrence |
|
| 637 | + // $daycount is the number of days per week that an occurrence occurs |
|
| 638 | + |
|
| 639 | + $weekskip = 0; |
|
| 640 | + if (($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek + $dayskip) > 6) { |
|
| 641 | + $weekskip = 1; |
|
| 642 | + } |
|
| 643 | + |
|
| 644 | + // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
|
| 645 | + // remaining occurrences based on the start of the recurrence. |
|
| 646 | + if (((int) $this->recur["term"]) == 0x22) { |
|
| 647 | + // $weekskip is the amount of weeks to skip from the startdate before the first occurrence |
|
| 648 | + // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that |
|
| 649 | + // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over |
|
| 650 | + // (eg when numoccur = 2, and daycount = 1) |
|
| 651 | + $forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount); |
|
| 652 | + |
|
| 653 | + // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one |
|
| 654 | + // for the occurrence on the first day |
|
| 655 | + $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1; |
|
| 656 | + |
|
| 657 | + // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence |
|
| 658 | + $forwardcount *= (int) $this->recur["everyn"]; |
|
| 659 | + } |
|
| 660 | + |
|
| 661 | + // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week) |
|
| 662 | + $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60); |
|
| 663 | + } |
|
| 664 | + |
|
| 665 | + // Calc first occ |
|
| 666 | + $firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60); |
|
| 667 | + |
|
| 668 | + $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60; |
|
| 669 | + |
|
| 670 | + if ($this->recur["regen"]) { |
|
| 671 | + $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1); |
|
| 672 | + } |
|
| 673 | + else { |
|
| 674 | + $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]); |
|
| 675 | + } |
|
| 676 | + |
|
| 677 | + break; |
|
| 678 | + |
|
| 679 | + case 0x0C: |
|
| 680 | + // Monthly |
|
| 681 | + case 0x0D: |
|
| 682 | + // Yearly |
|
| 683 | + if (!isset($this->recur["everyn"])) { |
|
| 684 | + return; |
|
| 685 | + } |
|
| 686 | + if ($term == 0x0D /* yearly */ && !isset($this->recur["month"])) { |
|
| 687 | + return; |
|
| 688 | + } |
|
| 689 | + |
|
| 690 | + if ($term == 0x0C /* monthly */) { |
|
| 691 | + $everyn = (int) $this->recur["everyn"]; |
|
| 692 | + } |
|
| 693 | + else { |
|
| 694 | + $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12; |
|
| 695 | + } |
|
| 696 | + |
|
| 697 | + // Get montday/month/year of original start |
|
| 698 | + $curmonthday = gmdate("j", (int) $this->recur["start"]); |
|
| 699 | + $curyear = gmdate("Y", (int) $this->recur["start"]); |
|
| 700 | + $curmonth = gmdate("n", (int) $this->recur["start"]); |
|
| 701 | + |
|
| 702 | + // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
|
| 703 | + // remaining occurrences based on the start of the recurrence. |
|
| 704 | + if (((int) $this->recur["term"]) == 0x22) { |
|
| 705 | + // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus |
|
| 706 | + // one to make sure there are always at least one occurrence left) |
|
| 707 | + $forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn); |
|
| 708 | + } |
|
| 709 | + |
|
| 710 | + // Get month for yearly on D'th day of month M |
|
| 711 | + if ($term == 0x0D /* yearly */) { |
|
| 712 | + $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg |
|
| 713 | + } |
|
| 714 | + |
|
| 715 | + switch ((int) $this->recur["subtype"]) { |
|
| 716 | + // on D day of every M month |
|
| 717 | + case 2: |
|
| 718 | + if (!isset($this->recur["monthday"])) { |
|
| 719 | + return; |
|
| 720 | + } |
|
| 721 | + // Recalc startdate |
|
| 722 | + |
|
| 723 | + // Set on the right begin day |
|
| 724 | + |
|
| 725 | + // Go the beginning of the month |
|
| 726 | + $this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60; |
|
| 727 | + // Go the the correct month day |
|
| 728 | + $this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60; |
|
| 729 | + |
|
| 730 | + // If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence |
|
| 731 | + if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) || |
|
| 732 | + ($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) { |
|
| 733 | + if ($term == 0x0D /* yearly */) { |
|
| 734 | + if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date |
|
| 735 | + $count = $everyn - ($curmonth - $selmonth); |
|
| 736 | + } |
|
| 737 | + elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date |
|
| 738 | + $count = $selmonth - $curmonth; |
|
| 739 | + } |
|
| 740 | + else { |
|
| 741 | + // Go to next occurrence while recurrence start date is greater than occurrence date but within same month |
|
| 742 | + if (((int) $this->recur["monthday"]) < $curmonthday) { |
|
| 743 | + $count = $everyn; |
|
| 744 | + } |
|
| 745 | + } |
|
| 746 | + } |
|
| 747 | + else { |
|
| 748 | + $count = $everyn; // Monthly, go to next occurrence in 'everyn' months |
|
| 749 | + } |
|
| 750 | + |
|
| 751 | + // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days |
|
| 752 | + for ($i = 0; $i < $count; ++$i) { |
|
| 753 | + $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 754 | + |
|
| 755 | + if ($curmonth == 12) { |
|
| 756 | + ++$curyear; |
|
| 757 | + $curmonth = 0; |
|
| 758 | + } |
|
| 759 | + ++$curmonth; |
|
| 760 | + } |
|
| 761 | + } |
|
| 762 | + |
|
| 763 | + // "start" is now pointing to the first occurrence, except that it will overshoot if the |
|
| 764 | + // month in which it occurs has less days than specified as the day of the month. So 31st |
|
| 765 | + // of each month will overshoot in february (29 days). We compensate for that by checking |
|
| 766 | + // if the day of the month we got is wrong, and then back up to the last day of the previous |
|
| 767 | + // month. |
|
| 768 | + if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 && |
|
| 769 | + gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) { |
|
| 770 | + $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60; |
|
| 771 | + } |
|
| 772 | + |
|
| 773 | + // "start" is now the first occurrence |
|
| 774 | + |
|
| 775 | + if ($term == 0x0C /* monthly */) { |
|
| 776 | + // Calc first occ |
|
| 777 | + $monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 778 | + |
|
| 779 | + $firstocc = 0; |
|
| 780 | + for ($i = 0; $i < $monthIndex; ++$i) { |
|
| 781 | + $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60; |
|
| 782 | + } |
|
| 783 | + |
|
| 784 | + $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
|
| 785 | + } |
|
| 786 | + else { |
|
| 787 | + // Calc first occ |
|
| 788 | + $firstocc = 0; |
|
| 789 | + $monthIndex = (int) gmdate("n", $this->recur["start"]); |
|
| 790 | + for ($i = 1; $i < $monthIndex; ++$i) { |
|
| 791 | + $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; |
|
| 792 | + } |
|
| 793 | + |
|
| 794 | + $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
|
| 795 | + } |
|
| 796 | + |
|
| 797 | + break; |
|
| 798 | + |
|
| 799 | + case 3: |
|
| 800 | + // monthly: on Nth weekday of every M month |
|
| 801 | + // yearly: on Nth weekday of M month |
|
| 802 | + if (!isset($this->recur["weekdays"], $this->recur["nday"])) { |
|
| 803 | + return; |
|
| 804 | + } |
|
| 805 | + |
|
| 806 | + $weekdays = (int) $this->recur["weekdays"]; |
|
| 807 | + $nday = (int) $this->recur["nday"]; |
|
| 808 | + |
|
| 809 | + // Calc startdate |
|
| 810 | + $monthbegindow = (int) $this->recur["start"]; |
|
| 811 | + |
|
| 812 | + if ($nday == 5) { |
|
| 813 | + // Set date on the last day of the last month |
|
| 814 | + $monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60; |
|
| 815 | + } |
|
| 816 | + else { |
|
| 817 | + // Set on the first day of the month |
|
| 818 | + $monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60); |
|
| 819 | + } |
|
| 820 | + |
|
| 821 | + if ($term == 0x0D /* yearly */) { |
|
| 822 | + // Set on right month |
|
| 823 | + if ($selmonth < $curmonth) { |
|
| 824 | + $tmp = 12 - $curmonth + $selmonth; |
|
| 825 | + } |
|
| 826 | + else { |
|
| 827 | + $tmp = ($selmonth - $curmonth); |
|
| 828 | + } |
|
| 829 | + |
|
| 830 | + for ($i = 0; $i < $tmp; ++$i) { |
|
| 831 | + $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 832 | + |
|
| 833 | + if ($curmonth == 12) { |
|
| 834 | + ++$curyear; |
|
| 835 | + $curmonth = 0; |
|
| 836 | + } |
|
| 837 | + ++$curmonth; |
|
| 838 | + } |
|
| 839 | + } |
|
| 840 | + else { |
|
| 841 | + // Check or you exist in the right month |
|
| 842 | + |
|
| 843 | + $dayofweek = gmdate("w", $monthbegindow); |
|
| 844 | + for ($i = 0; $i < 7; ++$i) { |
|
| 845 | + if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 846 | + $day = gmdate("j", $monthbegindow) - $i; |
|
| 847 | + |
|
| 848 | + break; |
|
| 849 | + } |
|
| 850 | + if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 851 | + $day = (($nday - 1) * 7) + ($i + 1); |
|
| 852 | + |
|
| 853 | + break; |
|
| 854 | + } |
|
| 855 | + } |
|
| 856 | + |
|
| 857 | + // Goto the next X month |
|
| 858 | + if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) { |
|
| 859 | + if ($nday == 5) { |
|
| 860 | + $monthbegindow += 24 * 60 * 60; |
|
| 861 | + if ($curmonth == 12) { |
|
| 862 | + ++$curyear; |
|
| 863 | + $curmonth = 0; |
|
| 864 | + } |
|
| 865 | + ++$curmonth; |
|
| 866 | + } |
|
| 867 | + |
|
| 868 | + for ($i = 0; $i < $everyn; ++$i) { |
|
| 869 | + $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 870 | + |
|
| 871 | + if ($curmonth == 12) { |
|
| 872 | + ++$curyear; |
|
| 873 | + $curmonth = 0; |
|
| 874 | + } |
|
| 875 | + ++$curmonth; |
|
| 876 | + } |
|
| 877 | + |
|
| 878 | + if ($nday == 5) { |
|
| 879 | + $monthbegindow -= 24 * 60 * 60; |
|
| 880 | + } |
|
| 881 | + } |
|
| 882 | + } |
|
| 883 | + |
|
| 884 | + // FIXME: weekstart? |
|
| 885 | + |
|
| 886 | + $day = 0; |
|
| 887 | + // Set start on the right day |
|
| 888 | + $dayofweek = gmdate("w", $monthbegindow); |
|
| 889 | + for ($i = 0; $i < 7; ++$i) { |
|
| 890 | + if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 891 | + $day = $i; |
|
| 892 | + |
|
| 893 | + break; |
|
| 894 | + } |
|
| 895 | + if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 896 | + $day = ($nday - 1) * 7 + ($i + 1); |
|
| 897 | + |
|
| 898 | + break; |
|
| 899 | + } |
|
| 900 | + } |
|
| 901 | + if ($nday == 5) { |
|
| 902 | + $monthbegindow -= $day * 24 * 60 * 60; |
|
| 903 | + } |
|
| 904 | + else { |
|
| 905 | + $monthbegindow += ($day - 1) * 24 * 60 * 60; |
|
| 906 | + } |
|
| 907 | + |
|
| 908 | + $firstocc = 0; |
|
| 909 | + |
|
| 910 | + if ($term == 0x0C /* monthly */) { |
|
| 911 | + // Calc first occ |
|
| 912 | + $monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 913 | + |
|
| 914 | + for ($i = 0; $i < $monthIndex; ++$i) { |
|
| 915 | + $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60; |
|
| 916 | + } |
|
| 917 | + |
|
| 918 | + $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
|
| 919 | + } |
|
| 920 | + else { |
|
| 921 | + // Calc first occ |
|
| 922 | + $monthIndex = (int) gmdate("n", $this->recur["start"]); |
|
| 923 | + |
|
| 924 | + for ($i = 1; $i < $monthIndex; ++$i) { |
|
| 925 | + $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; |
|
| 926 | + } |
|
| 927 | + |
|
| 928 | + $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
|
| 929 | + } |
|
| 930 | + |
|
| 931 | + break; |
|
| 932 | + } |
|
| 933 | + |
|
| 934 | + break; |
|
| 935 | + } |
|
| 936 | + |
|
| 937 | + if (!isset($this->recur["term"])) { |
|
| 938 | + return; |
|
| 939 | + } |
|
| 940 | + |
|
| 941 | + // Terminate |
|
| 942 | + $term = (int) $this->recur["term"]; |
|
| 943 | + $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00); |
|
| 944 | + |
|
| 945 | + switch ($term) { |
|
| 946 | + // After the given enddate |
|
| 947 | + case 0x21: |
|
| 948 | + $rdata .= pack("V", 10); |
|
| 949 | + |
|
| 950 | + break; |
|
| 951 | + // After a number of times |
|
| 952 | + case 0x22: |
|
| 953 | + if (!isset($this->recur["numoccur"])) { |
|
| 954 | + return; |
|
| 955 | + } |
|
| 956 | + |
|
| 957 | + $rdata .= pack("V", (int) $this->recur["numoccur"]); |
|
| 958 | + |
|
| 959 | + break; |
|
| 960 | + // Never ends |
|
| 961 | + case 0x23: |
|
| 962 | + $rdata .= pack("V", 0); |
|
| 963 | + |
|
| 964 | + break; |
|
| 965 | + } |
|
| 966 | + |
|
| 967 | + // Strange little thing for the recurrence type "every workday" |
|
| 968 | + if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) { |
|
| 969 | + $rdata .= pack("V", 1); |
|
| 970 | + } |
|
| 971 | + else { // Other recurrences |
|
| 972 | + $rdata .= pack("V", 0); |
|
| 973 | + } |
|
| 974 | + |
|
| 975 | + // Exception data |
|
| 976 | + |
|
| 977 | + // Get all exceptions |
|
| 978 | + $deleted_items = $this->recur["deleted_occurrences"]; |
|
| 979 | + $changed_items = $this->recur["changed_occurrences"]; |
|
| 980 | + |
|
| 981 | + // Merge deleted and changed items into one list |
|
| 982 | + $items = $deleted_items; |
|
| 983 | + |
|
| 984 | + foreach ($changed_items as $changed_item) { |
|
| 985 | + array_push($items, $changed_item["basedate"]); |
|
| 986 | + } |
|
| 987 | + |
|
| 988 | + sort($items); |
|
| 989 | + |
|
| 990 | + // Add the merged list in to the rdata |
|
| 991 | + $rdata .= pack("V", count($items)); |
|
| 992 | + foreach ($items as $item) { |
|
| 993 | + $rdata .= pack("V", $this->unixDataToRecurData($item)); |
|
| 994 | + } |
|
| 995 | + |
|
| 996 | + // Loop through the changed exceptions (not deleted) |
|
| 997 | + $rdata .= pack("V", count($changed_items)); |
|
| 998 | + $items = []; |
|
| 999 | + |
|
| 1000 | + foreach ($changed_items as $changed_item) { |
|
| 1001 | + $items[] = $this->dayStartOf($changed_item["start"]); |
|
| 1002 | + } |
|
| 1003 | + |
|
| 1004 | + sort($items); |
|
| 1005 | + |
|
| 1006 | + // Add the changed items list int the rdata |
|
| 1007 | + foreach ($items as $item) { |
|
| 1008 | + $rdata .= pack("V", $this->unixDataToRecurData($item)); |
|
| 1009 | + } |
|
| 1010 | + |
|
| 1011 | + // Set start date |
|
| 1012 | + $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"])); |
|
| 1013 | + |
|
| 1014 | + // Set enddate |
|
| 1015 | + switch ($term) { |
|
| 1016 | + // After the given enddate |
|
| 1017 | + case 0x21: |
|
| 1018 | + $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
|
| 1019 | + |
|
| 1020 | + break; |
|
| 1021 | + // After a number of times |
|
| 1022 | + case 0x22: |
|
| 1023 | + // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour |
|
| 1024 | + $occenddate = (int) $this->recur["start"]; |
|
| 1025 | + |
|
| 1026 | + switch ((int) $this->recur["type"]) { |
|
| 1027 | + case 0x0A: // daily |
|
| 1028 | + if ($this->recur["subtype"] == 1) { |
|
| 1029 | + // Daily every workday |
|
| 1030 | + $restocc = (int) $this->recur["numoccur"]; |
|
| 1031 | + |
|
| 1032 | + // Get starting weekday |
|
| 1033 | + $nowtime = $this->gmtime($occenddate); |
|
| 1034 | + $j = $nowtime["tm_wday"]; |
|
| 1035 | + |
|
| 1036 | + while (1) { |
|
| 1037 | + if (($j % 7) > 0 && ($j % 7) < 6) { |
|
| 1038 | + --$restocc; |
|
| 1039 | + } |
|
| 1040 | + |
|
| 1041 | + ++$j; |
|
| 1042 | + |
|
| 1043 | + if ($restocc <= 0) { |
|
| 1044 | + break; |
|
| 1045 | + } |
|
| 1046 | + |
|
| 1047 | + $occenddate += 24 * 60 * 60; |
|
| 1048 | + } |
|
| 1049 | + } |
|
| 1050 | + else { |
|
| 1051 | + // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence) |
|
| 1052 | + $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1))); |
|
| 1053 | + } |
|
| 1054 | + |
|
| 1055 | + break; |
|
| 1056 | + |
|
| 1057 | + case 0x0B: // weekly |
|
| 1058 | + // Needed values |
|
| 1059 | + // $forwardcount - number of weeks we can skip forward |
|
| 1060 | + // $restocc - number of remaining occurrences after the week skip |
|
| 1061 | + |
|
| 1062 | + // Add the weeks till the last item |
|
| 1063 | + $occenddate += ($forwardcount * 7 * 24 * 60 * 60); |
|
| 1064 | + |
|
| 1065 | + $dayofweek = gmdate("w", $occenddate); |
|
| 1066 | + |
|
| 1067 | + // Loop through the last occurrences until we have had them all |
|
| 1068 | + for ($j = 1; $restocc > 0; ++$j) { |
|
| 1069 | + // Jump to the next week (which may be N weeks away) when going over the week boundary |
|
| 1070 | + if ((($dayofweek + $j) % 7) == $weekstart) { |
|
| 1071 | + $occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60; |
|
| 1072 | + } |
|
| 1073 | + |
|
| 1074 | + // If this is a matching day, once less occurrence to process |
|
| 1075 | + if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 1076 | + --$restocc; |
|
| 1077 | + } |
|
| 1078 | + |
|
| 1079 | + // Next day |
|
| 1080 | + $occenddate += 24 * 60 * 60; |
|
| 1081 | + } |
|
| 1082 | + |
|
| 1083 | + break; |
|
| 1084 | + |
|
| 1085 | + case 0x0C: // monthly |
|
| 1086 | + case 0x0D: // yearly |
|
| 1087 | + $curyear = gmdate("Y", (int) $this->recur["start"]); |
|
| 1088 | + $curmonth = gmdate("n", (int) $this->recur["start"]); |
|
| 1089 | + // $forwardcount = months |
|
| 1090 | + |
|
| 1091 | + switch ((int) $this->recur["subtype"]) { |
|
| 1092 | + case 2: // on D day of every M month |
|
| 1093 | + while ($forwardcount > 0) { |
|
| 1094 | + $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 1095 | + |
|
| 1096 | + if ($curmonth >= 12) { |
|
| 1097 | + $curmonth = 1; |
|
| 1098 | + ++$curyear; |
|
| 1099 | + } |
|
| 1100 | + else { |
|
| 1101 | + ++$curmonth; |
|
| 1102 | + } |
|
| 1103 | + --$forwardcount; |
|
| 1104 | + } |
|
| 1105 | + |
|
| 1106 | + // compensation between 28 and 31 |
|
| 1107 | + if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 && |
|
| 1108 | + gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) { |
|
| 1109 | + if (gmdate("j", $occenddate) < 28) { |
|
| 1110 | + $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60; |
|
| 1111 | + } |
|
| 1112 | + else { |
|
| 1113 | + $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; |
|
| 1114 | + } |
|
| 1115 | + } |
|
| 1116 | + |
|
| 1117 | + break; |
|
| 1118 | + |
|
| 1119 | + case 3: // on Nth weekday of every M month |
|
| 1120 | + $nday = (int) $this->recur["nday"]; // 1 tot 5 |
|
| 1121 | + $weekdays = (int) $this->recur["weekdays"]; |
|
| 1122 | + |
|
| 1123 | + while ($forwardcount > 0) { |
|
| 1124 | + $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
|
| 1125 | + if ($curmonth >= 12) { |
|
| 1126 | + $curmonth = 1; |
|
| 1127 | + ++$curyear; |
|
| 1128 | + } |
|
| 1129 | + else { |
|
| 1130 | + ++$curmonth; |
|
| 1131 | + } |
|
| 1132 | + |
|
| 1133 | + --$forwardcount; |
|
| 1134 | + } |
|
| 1135 | + |
|
| 1136 | + if ($nday == 5) { |
|
| 1137 | + // Set date on the last day of the last month |
|
| 1138 | + $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; |
|
| 1139 | + } |
|
| 1140 | + else { |
|
| 1141 | + // Set date on the first day of the last month |
|
| 1142 | + $occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60; |
|
| 1143 | + } |
|
| 1144 | + |
|
| 1145 | + $dayofweek = gmdate("w", $occenddate); |
|
| 1146 | + for ($i = 0; $i < 7; ++$i) { |
|
| 1147 | + if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 1148 | + $occenddate -= $i * 24 * 60 * 60; |
|
| 1149 | + |
|
| 1150 | + break; |
|
| 1151 | + } |
|
| 1152 | + if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 1153 | + $occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60; |
|
| 1154 | + |
|
| 1155 | + break; |
|
| 1156 | + } |
|
| 1157 | + } |
|
| 1158 | + |
|
| 1159 | + break; // case 3: |
|
| 1160 | + } |
|
| 1161 | + |
|
| 1162 | + break; |
|
| 1163 | + } |
|
| 1164 | + |
|
| 1165 | + if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) { |
|
| 1166 | + $occenddate = PHP_INT_MAX; |
|
| 1167 | + } |
|
| 1168 | + |
|
| 1169 | + $this->recur["end"] = $occenddate; |
|
| 1170 | + |
|
| 1171 | + $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
|
| 1172 | + |
|
| 1173 | + break; |
|
| 1174 | + // Never ends |
|
| 1175 | + case 0x23: |
|
| 1176 | + default: |
|
| 1177 | + $this->recur["end"] = 0x7FFFFFFF; // max date -> 2038 |
|
| 1178 | + $rdata .= pack("V", 0x5AE980DF); |
|
| 1179 | + |
|
| 1180 | + break; |
|
| 1181 | + } |
|
| 1182 | + |
|
| 1183 | + // UTC date |
|
| 1184 | + $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]); |
|
| 1185 | + $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]); |
|
| 1186 | + |
|
| 1187 | + // utc date+time |
|
| 1188 | + $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart; |
|
| 1189 | + $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart; |
|
| 1190 | + |
|
| 1191 | + $propsToSet = []; |
|
| 1192 | + // update reminder time |
|
| 1193 | + $propsToSet[$this->proptags["reminder_time"]] = $utcfirstoccstartdatetime; |
|
| 1194 | + |
|
| 1195 | + // update first occurrence date |
|
| 1196 | + $propsToSet[$this->proptags["startdate"]] = $propsToSet[$this->proptags["commonstart"]] = $utcfirstoccstartdatetime; |
|
| 1197 | + $propsToSet[$this->proptags["duedate"]] = $propsToSet[$this->proptags["commonend"]] = $utcfirstoccenddatetime; |
|
| 1198 | + |
|
| 1199 | + // Set Outlook properties, if it is an appointment |
|
| 1200 | + if (isset($this->messageprops[$this->proptags["message_class"]]) && $this->messageprops[$this->proptags["message_class"]] == "IPM.Appointment") { |
|
| 1201 | + // update real begin and real end date |
|
| 1202 | + $propsToSet[$this->proptags["startdate_recurring"]] = $utcstart; |
|
| 1203 | + $propsToSet[$this->proptags["enddate_recurring"]] = $utcend; |
|
| 1204 | + |
|
| 1205 | + // recurrencetype |
|
| 1206 | + // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype |
|
| 1207 | + $propsToSet[$this->proptags["recurrencetype"]] = ((int) $this->recur["type"]) - 0x9; |
|
| 1208 | + |
|
| 1209 | + // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting |
|
| 1210 | + $propsToSet[$this->proptags["side_effects"]] = 369; |
|
| 1211 | + } |
|
| 1212 | + else { |
|
| 1213 | + $propsToSet[$this->proptags["side_effects"]] = 3441; |
|
| 1214 | + } |
|
| 1215 | + |
|
| 1216 | + // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog |
|
| 1217 | + // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset |
|
| 1218 | + // to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to |
|
| 1219 | + // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time) |
|
| 1220 | + // with the reminder flag set. |
|
| 1221 | + $reminderprops = mapi_getprops($this->message, [$this->proptags["reminder_minutes"]]); |
|
| 1222 | + if (isset($reminderprops[$this->proptags["reminder_minutes"]])) { |
|
| 1223 | + $occ = false; |
|
| 1224 | + $occurrences = $this->getItems(time(), 0x7FF00000, 3, true); |
|
| 1225 | + |
|
| 1226 | + for ($i = 0, $len = count($occurrences); $i < $len; ++$i) { |
|
| 1227 | + // This will actually also give us appointments that have already started, but not yet ended. Since we want the next |
|
| 1228 | + // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum |
|
| 1229 | + // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case: |
|
| 1230 | + // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item |
|
| 1231 | + // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped. |
|
| 1232 | + |
|
| 1233 | + if (($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) { |
|
| 1234 | + $occ = $occurrences[$i]; |
|
| 1235 | + |
|
| 1236 | + break; |
|
| 1237 | + } |
|
| 1238 | + } |
|
| 1239 | + |
|
| 1240 | + if ($occ) { |
|
| 1241 | + $propsToSet[$this->proptags["flagdueby"]] = $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60); |
|
| 1242 | + } |
|
| 1243 | + else { |
|
| 1244 | + // Last reminder passed, no reminders any more. |
|
| 1245 | + $propsToSet[$this->proptags["reminder"]] = false; |
|
| 1246 | + $propsToSet[$this->proptags["flagdueby"]] = 0x7FF00000; |
|
| 1247 | + } |
|
| 1248 | + } |
|
| 1249 | + |
|
| 1250 | + // Default data |
|
| 1251 | + // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information) |
|
| 1252 | + $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00); |
|
| 1253 | + |
|
| 1254 | + if (isset($this->recur["startocc"], $this->recur["endocc"])) { |
|
| 1255 | + // Set start and endtime in minutes |
|
| 1256 | + $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]); |
|
| 1257 | + } |
|
| 1258 | + |
|
| 1259 | + // Detailed exception data |
|
| 1260 | + |
|
| 1261 | + $changed_items = $this->recur["changed_occurrences"]; |
|
| 1262 | + |
|
| 1263 | + $rdata .= pack("v", count($changed_items)); |
|
| 1264 | + |
|
| 1265 | + foreach ($changed_items as $changed_item) { |
|
| 1266 | + // Set start and end time of exception |
|
| 1267 | + $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); |
|
| 1268 | + $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); |
|
| 1269 | + $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); |
|
| 1270 | + |
|
| 1271 | + // Bitmask |
|
| 1272 | + $bitmask = 0; |
|
| 1273 | + |
|
| 1274 | + // Check for changed strings |
|
| 1275 | + if (isset($changed_item["subject"])) { |
|
| 1276 | + $bitmask |= 1 << 0; |
|
| 1277 | + } |
|
| 1278 | + |
|
| 1279 | + if (isset($changed_item["remind_before"])) { |
|
| 1280 | + $bitmask |= 1 << 2; |
|
| 1281 | + } |
|
| 1282 | + |
|
| 1283 | + if (isset($changed_item["reminder_set"])) { |
|
| 1284 | + $bitmask |= 1 << 3; |
|
| 1285 | + } |
|
| 1286 | + |
|
| 1287 | + if (isset($changed_item["location"])) { |
|
| 1288 | + $bitmask |= 1 << 4; |
|
| 1289 | + } |
|
| 1290 | + |
|
| 1291 | + if (isset($changed_item["busystatus"])) { |
|
| 1292 | + $bitmask |= 1 << 5; |
|
| 1293 | + } |
|
| 1294 | + |
|
| 1295 | + if (isset($changed_item["alldayevent"])) { |
|
| 1296 | + $bitmask |= 1 << 7; |
|
| 1297 | + } |
|
| 1298 | + |
|
| 1299 | + if (isset($changed_item["label"])) { |
|
| 1300 | + $bitmask |= 1 << 8; |
|
| 1301 | + } |
|
| 1302 | + |
|
| 1303 | + $rdata .= pack("v", $bitmask); |
|
| 1304 | + |
|
| 1305 | + // Set "subject" |
|
| 1306 | + if (isset($changed_item["subject"])) { |
|
| 1307 | + // convert utf-8 to non-unicode blob string (us-ascii?) |
|
| 1308 | + $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]); |
|
| 1309 | + $length = strlen($subject); |
|
| 1310 | + $rdata .= pack("vv", $length + 1, $length); |
|
| 1311 | + $rdata .= pack("a" . $length, $subject); |
|
| 1312 | + } |
|
| 1313 | + |
|
| 1314 | + if (isset($changed_item["remind_before"])) { |
|
| 1315 | + $rdata .= pack("V", $changed_item["remind_before"]); |
|
| 1316 | + } |
|
| 1317 | + |
|
| 1318 | + if (isset($changed_item["reminder_set"])) { |
|
| 1319 | + $rdata .= pack("V", $changed_item["reminder_set"]); |
|
| 1320 | + } |
|
| 1321 | + |
|
| 1322 | + if (isset($changed_item["location"])) { |
|
| 1323 | + $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]); |
|
| 1324 | + $length = strlen($location); |
|
| 1325 | + $rdata .= pack("vv", $length + 1, $length); |
|
| 1326 | + $rdata .= pack("a" . $length, $location); |
|
| 1327 | + } |
|
| 1328 | + |
|
| 1329 | + if (isset($changed_item["busystatus"])) { |
|
| 1330 | + $rdata .= pack("V", $changed_item["busystatus"]); |
|
| 1331 | + } |
|
| 1332 | + |
|
| 1333 | + if (isset($changed_item["alldayevent"])) { |
|
| 1334 | + $rdata .= pack("V", $changed_item["alldayevent"]); |
|
| 1335 | + } |
|
| 1336 | + |
|
| 1337 | + if (isset($changed_item["label"])) { |
|
| 1338 | + $rdata .= pack("V", $changed_item["label"]); |
|
| 1339 | + } |
|
| 1340 | + } |
|
| 1341 | + |
|
| 1342 | + $rdata .= pack("V", 0); |
|
| 1343 | + |
|
| 1344 | + // write extended data |
|
| 1345 | + foreach ($changed_items as $changed_item) { |
|
| 1346 | + $rdata .= pack("V", 0); |
|
| 1347 | + if (isset($changed_item["subject"]) || isset($changed_item["location"])) { |
|
| 1348 | + $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); |
|
| 1349 | + $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); |
|
| 1350 | + $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); |
|
| 1351 | + } |
|
| 1352 | + |
|
| 1353 | + if (isset($changed_item["subject"])) { |
|
| 1354 | + $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]); |
|
| 1355 | + $length = iconv_strlen($subject, "UCS-2LE"); |
|
| 1356 | + $rdata .= pack("v", $length); |
|
| 1357 | + $rdata .= pack("a" . $length * 2, $subject); |
|
| 1358 | + } |
|
| 1359 | + |
|
| 1360 | + if (isset($changed_item["location"])) { |
|
| 1361 | + $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]); |
|
| 1362 | + $length = iconv_strlen($location, "UCS-2LE"); |
|
| 1363 | + $rdata .= pack("v", $length); |
|
| 1364 | + $rdata .= pack("a" . $length * 2, $location); |
|
| 1365 | + } |
|
| 1366 | + |
|
| 1367 | + if (isset($changed_item["subject"]) || isset($changed_item["location"])) { |
|
| 1368 | + $rdata .= pack("V", 0); |
|
| 1369 | + } |
|
| 1370 | + } |
|
| 1371 | + |
|
| 1372 | + $rdata .= pack("V", 0); |
|
| 1373 | + |
|
| 1374 | + // Set props |
|
| 1375 | + $propsToSet[$this->proptags["recurring_data"]] = $rdata; |
|
| 1376 | + $propsToSet[$this->proptags["recurring"]] = true; |
|
| 1377 | + |
|
| 1378 | + if (isset($this->tz) && $this->tz) { |
|
| 1379 | + $timezone = "GMT"; |
|
| 1380 | + if ($this->tz["timezone"] != 0) { |
|
| 1381 | + // Create user readable timezone information |
|
| 1382 | + $timezone = sprintf( |
|
| 1383 | + "(GMT %s%02d:%02d)", |
|
| 1384 | + (-$this->tz["timezone"] > 0 ? "+" : "-"), |
|
| 1385 | + abs($this->tz["timezone"] / 60), |
|
| 1386 | + abs($this->tz["timezone"] % 60) |
|
| 1387 | + ); |
|
| 1388 | + } |
|
| 1389 | + $propsToSet[$this->proptags["timezone_data"]] = $this->getTimezoneData($this->tz); |
|
| 1390 | + $propsToSet[$this->proptags["timezone"]] = $timezone; |
|
| 1391 | + } |
|
| 1392 | + mapi_setprops($this->message, $propsToSet); |
|
| 1393 | + } |
|
| 1394 | + |
|
| 1395 | + /** |
|
| 1396 | + * Function which converts a recurrence date timestamp to an unix date timestamp. |
|
| 1397 | + * |
|
| 1398 | + * @author Steve Hardy |
|
| 1399 | + * |
|
| 1400 | + * @param int $rdate the date which will be converted |
|
| 1401 | + * |
|
| 1402 | + * @return int the converted date |
|
| 1403 | + */ |
|
| 1404 | + public function recurDataToUnixData($rdate) { |
|
| 1405 | + return ($rdate - 194074560) * 60; |
|
| 1406 | + } |
|
| 1407 | + |
|
| 1408 | + /** |
|
| 1409 | + * Function which converts an unix date timestamp to recurrence date timestamp. |
|
| 1410 | + * |
|
| 1411 | + * @author Johnny Biemans |
|
| 1412 | + * |
|
| 1413 | + * @param Date $date the date which will be converted |
|
| 1414 | + * |
|
| 1415 | + * @return int the converted date in minutes |
|
| 1416 | + */ |
|
| 1417 | + public function unixDataToRecurData($date) { |
|
| 1418 | + return ($date / 60) + 194074560; |
|
| 1419 | + } |
|
| 1420 | + |
|
| 1421 | + /** |
|
| 1422 | + * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves. |
|
| 1423 | + * |
|
| 1424 | + * @author Steve Hardy |
|
| 1425 | + * |
|
| 1426 | + * @param mixed $ts |
|
| 1427 | + */ |
|
| 1428 | + public function GetTZOffset($ts) { |
|
| 1429 | + $Offset = date("O", $ts); |
|
| 1430 | + |
|
| 1431 | + $Parity = $Offset < 0 ? -1 : 1; |
|
| 1432 | + $Offset = $Parity * $Offset; |
|
| 1433 | + $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100; |
|
| 1434 | + |
|
| 1435 | + return $Parity * $Offset; |
|
| 1436 | + } |
|
| 1437 | + |
|
| 1438 | + /** |
|
| 1439 | + * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves. |
|
| 1440 | + * |
|
| 1441 | + * @author Steve Hardy |
|
| 1442 | + * |
|
| 1443 | + * @param Date $time |
|
| 1444 | + * |
|
| 1445 | + * @return Date GMT Time |
|
| 1446 | + */ |
|
| 1447 | + public function gmtime($time) { |
|
| 1448 | + $TZOffset = $this->GetTZOffset($time); |
|
| 1449 | + |
|
| 1450 | + $t_time = $time - $TZOffset * 60; # Counter adjust for localtime() |
|
| 1451 | + |
|
| 1452 | + return localtime($t_time, 1); |
|
| 1453 | + } |
|
| 1454 | + |
|
| 1455 | + public function isLeapYear($year) { |
|
| 1456 | + return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0); |
|
| 1457 | + } |
|
| 1458 | + |
|
| 1459 | + public function getMonthInSeconds($year, $month) { |
|
| 1460 | + if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) { |
|
| 1461 | + $day = 31; |
|
| 1462 | + } |
|
| 1463 | + elseif (in_array($month, [4, 6, 9, 11])) { |
|
| 1464 | + $day = 30; |
|
| 1465 | + } |
|
| 1466 | + else { |
|
| 1467 | + $day = 28; |
|
| 1468 | + if ($this->isLeapYear($year) == 1) { |
|
| 1469 | + ++$day; |
|
| 1470 | + } |
|
| 1471 | + } |
|
| 1472 | + |
|
| 1473 | + return $day * 24 * 60 * 60; |
|
| 1474 | + } |
|
| 1475 | + |
|
| 1476 | + /** |
|
| 1477 | + * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour. |
|
| 1478 | + * |
|
| 1479 | + * @param int $year |
|
| 1480 | + * @param int $month |
|
| 1481 | + * @param int $week |
|
| 1482 | + * @param int $day |
|
| 1483 | + * @param int $hour |
|
| 1484 | + * |
|
| 1485 | + * @return returns the timestamp of the given date, timezone-independant |
|
| 1486 | + */ |
|
| 1487 | + public function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) { |
|
| 1488 | + // get first day of month |
|
| 1489 | + $date = gmmktime(0, 0, 0, $month, 0, $year + 1900); |
|
| 1490 | + |
|
| 1491 | + // get wday info |
|
| 1492 | + $gmdate = $this->gmtime($date); |
|
| 1493 | + |
|
| 1494 | + $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week |
|
| 1495 | + |
|
| 1496 | + $date += $week * 7 * 24 * 60 * 60; // go to correct week nr |
|
| 1497 | + $date += $day * 24 * 60 * 60; |
|
| 1498 | + $date += $hour * 60 * 60; |
|
| 1499 | + |
|
| 1500 | + $gmdate = $this->gmtime($date); |
|
| 1501 | + |
|
| 1502 | + // if we are in the next month, then back up a week, because week '5' means |
|
| 1503 | + // 'last week of month' |
|
| 1504 | + |
|
| 1505 | + if ($month != $gmdate["tm_mon"] + 1) { |
|
| 1506 | + $date -= 7 * 24 * 60 * 60; |
|
| 1507 | + } |
|
| 1508 | + |
|
| 1509 | + return $date; |
|
| 1510 | + } |
|
| 1511 | + |
|
| 1512 | + /** |
|
| 1513 | + * getTimezone gives the timezone offset (in minutes) of the given |
|
| 1514 | + * local date/time according to the given TZ info. |
|
| 1515 | + * |
|
| 1516 | + * @param mixed $tz |
|
| 1517 | + * @param mixed $date |
|
| 1518 | + */ |
|
| 1519 | + public function getTimezone($tz, $date) { |
|
| 1520 | + // No timezone -> GMT (+0) |
|
| 1521 | + if (!isset($tz["timezone"])) { |
|
| 1522 | + return 0; |
|
| 1523 | + } |
|
| 1524 | + |
|
| 1525 | + $dst = false; |
|
| 1526 | + $gmdate = $this->gmtime($date); |
|
| 1527 | + |
|
| 1528 | + $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]); |
|
| 1529 | + $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]); |
|
| 1530 | + |
|
| 1531 | + if ($dststart <= $dstend) { |
|
| 1532 | + // Northern hemisphere, eg DST is during Mar-Oct |
|
| 1533 | + if ($date > $dststart && $date < $dstend) { |
|
| 1534 | + $dst = true; |
|
| 1535 | + } |
|
| 1536 | + } |
|
| 1537 | + else { |
|
| 1538 | + // Southern hemisphere, eg DST is during Oct-Mar |
|
| 1539 | + if ($date < $dstend || $date > $dststart) { |
|
| 1540 | + $dst = true; |
|
| 1541 | + } |
|
| 1542 | + } |
|
| 1543 | + |
|
| 1544 | + if ($dst) { |
|
| 1545 | + return $tz["timezone"] + $tz["timezonedst"]; |
|
| 1546 | + } |
|
| 1547 | + |
|
| 1548 | + return $tz["timezone"]; |
|
| 1549 | + } |
|
| 1550 | + |
|
| 1551 | + /** |
|
| 1552 | + * getWeekNr() returns the week nr of the month (ie first week of february is 1). |
|
| 1553 | + * |
|
| 1554 | + * @param mixed $date |
|
| 1555 | + */ |
|
| 1556 | + public function getWeekNr($date) { |
|
| 1557 | + $gmdate = gmtime($date); |
|
| 1558 | + $gmdate["tm_mday"] = 0; |
|
| 1559 | + |
|
| 1560 | + return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1; |
|
| 1561 | + } |
|
| 1562 | + |
|
| 1563 | + /** |
|
| 1564 | + * parseTimezone parses the timezone as specified in named property 0x8233 |
|
| 1565 | + * in Outlook calendar messages. Returns the timezone in minutes negative |
|
| 1566 | + * offset (GMT +2:00 -> -120). |
|
| 1567 | + * |
|
| 1568 | + * @param mixed $data |
|
| 1569 | + */ |
|
| 1570 | + public function parseTimezone($data) { |
|
| 1571 | + if (strlen($data) < 48) { |
|
| 1572 | + return; |
|
| 1573 | + } |
|
| 1574 | + |
|
| 1575 | + return unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data); |
|
| 1576 | + } |
|
| 1577 | + |
|
| 1578 | + public function getTimezoneData($tz) { |
|
| 1579 | + return pack("lllllvvllvlvvlv", $tz["timezone"], 0, $tz["timezonedst"], 0, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendhour"], 0, 0, 0, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststarthour"], 0, 0); |
|
| 1580 | + } |
|
| 1581 | + |
|
| 1582 | + /** |
|
| 1583 | + * createTimezone creates the timezone as specified in the named property 0x8233 |
|
| 1584 | + * see also parseTimezone() |
|
| 1585 | + * $tz is an array with the timezone data. |
|
| 1586 | + * |
|
| 1587 | + * @param mixed $tz |
|
| 1588 | + */ |
|
| 1589 | + public function createTimezone($tz) { |
|
| 1590 | + return pack( |
|
| 1591 | + "lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx", |
|
| 1592 | + $tz["timezone"], |
|
| 1593 | + array_key_exists("timezonedst", $tz) ? $tz["timezonedst"] : 0, |
|
| 1594 | + array_key_exists("dstendmonth", $tz) ? $tz["dstendmonth"] : 0, |
|
| 1595 | + array_key_exists("dstendweek", $tz) ? $tz["dstendweek"] : 0, |
|
| 1596 | + array_key_exists("dstendhour", $tz) ? $tz["dstendhour"] : 0, |
|
| 1597 | + array_key_exists("dststartmonth", $tz) ? $tz["dststartmonth"] : 0, |
|
| 1598 | + array_key_exists("dststartweek", $tz) ? $tz["dststartweek"] : 0, |
|
| 1599 | + array_key_exists("dststarthour", $tz) ? $tz["dststarthour"] : 0 |
|
| 1600 | + ); |
|
| 1601 | + } |
|
| 1602 | + |
|
| 1603 | + /** |
|
| 1604 | + * toGMT returns a timestamp in GMT time for the time and timezone given. |
|
| 1605 | + * |
|
| 1606 | + * @param mixed $tz |
|
| 1607 | + * @param mixed $date |
|
| 1608 | + */ |
|
| 1609 | + public function toGMT($tz, $date) { |
|
| 1610 | + if (!isset($tz['timezone'])) { |
|
| 1611 | + return $date; |
|
| 1612 | + } |
|
| 1613 | + $offset = $this->getTimezone($tz, $date); |
|
| 1614 | + |
|
| 1615 | + return $date + $offset * 60; |
|
| 1616 | + } |
|
| 1617 | + |
|
| 1618 | + /** |
|
| 1619 | + * fromGMT returns a timestamp in the local timezone given from the GMT time given. |
|
| 1620 | + * |
|
| 1621 | + * @param mixed $tz |
|
| 1622 | + * @param mixed $date |
|
| 1623 | + */ |
|
| 1624 | + public function fromGMT($tz, $date) { |
|
| 1625 | + $offset = $this->getTimezone($tz, $date); |
|
| 1626 | + |
|
| 1627 | + return $date - $offset * 60; |
|
| 1628 | + } |
|
| 1629 | + |
|
| 1630 | + /** |
|
| 1631 | + * Function to get timestamp of the beginning of the day of the timestamp given. |
|
| 1632 | + * |
|
| 1633 | + * @param date $date |
|
| 1634 | + * |
|
| 1635 | + * @return date timestamp referring to same day but at 00:00:00 |
|
| 1636 | + */ |
|
| 1637 | + public function dayStartOf($date) { |
|
| 1638 | + $time1 = $this->gmtime($date); |
|
| 1639 | + |
|
| 1640 | + return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900); |
|
| 1641 | + } |
|
| 1642 | + |
|
| 1643 | + /** |
|
| 1644 | + * Function to get timestamp of the beginning of the month of the timestamp given. |
|
| 1645 | + * |
|
| 1646 | + * @param date $date |
|
| 1647 | + * |
|
| 1648 | + * @return date Timestamp referring to same month but on the first day, and at 00:00:00 |
|
| 1649 | + */ |
|
| 1650 | + public function monthStartOf($date) { |
|
| 1651 | + $time1 = $this->gmtime($date); |
|
| 1652 | + |
|
| 1653 | + return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900); |
|
| 1654 | + } |
|
| 1655 | + |
|
| 1656 | + /** |
|
| 1657 | + * Function to get timestamp of the beginning of the year of the timestamp given. |
|
| 1658 | + * |
|
| 1659 | + * @param date $date |
|
| 1660 | + * |
|
| 1661 | + * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00 |
|
| 1662 | + */ |
|
| 1663 | + public function yearStartOf($date) { |
|
| 1664 | + $time1 = $this->gmtime($date); |
|
| 1665 | + |
|
| 1666 | + return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900); |
|
| 1667 | + } |
|
| 1668 | + |
|
| 1669 | + /** |
|
| 1670 | + * Function which returns the items in a given interval. This included expansion of the recurrence and |
|
| 1671 | + * processing of exceptions (modified and deleted). |
|
| 1672 | + * |
|
| 1673 | + * @param string $entryid the entryid of the message |
|
| 1674 | + * @param array $props the properties of the message |
|
| 1675 | + * @param date $start start time of the interval (GMT) |
|
| 1676 | + * @param date $end end time of the interval (GMT) |
|
| 1677 | + * @param mixed $limit |
|
| 1678 | + * @param mixed $remindersonly |
|
| 1679 | + */ |
|
| 1680 | + public function getItems($start, $end, $limit = 0, $remindersonly = false) { |
|
| 1681 | + $items = []; |
|
| 1682 | + |
|
| 1683 | + if (isset($this->recur)) { |
|
| 1684 | + // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which |
|
| 1685 | + // exceptions are in range and have a reminder set |
|
| 1686 | + if ($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) { |
|
| 1687 | + // Sort exceptions by start time |
|
| 1688 | + uasort($this->recur["changed_occurrences"], [$this, "sortExceptionStart"]); |
|
| 1689 | + |
|
| 1690 | + // Loop through all changed exceptions |
|
| 1691 | + foreach ($this->recur["changed_occurrences"] as $exception) { |
|
| 1692 | + // Check reminder set |
|
| 1693 | + if (!isset($exception["reminder"]) || $exception["reminder"] == false) { |
|
| 1694 | + continue; |
|
| 1695 | + } |
|
| 1696 | + |
|
| 1697 | + // Convert to GMT |
|
| 1698 | + $occstart = $this->toGMT($this->tz, $exception["start"]); // seb changed $tz to $this->tz |
|
| 1699 | + $occend = $this->toGMT($this->tz, $exception["end"]); // seb changed $tz to $this->tz |
|
| 1700 | + |
|
| 1701 | + // Check range criterium |
|
| 1702 | + if ($occstart > $end || $occend < $start) { |
|
| 1703 | + continue; |
|
| 1704 | + } |
|
| 1705 | + |
|
| 1706 | + // OK, add to items. |
|
| 1707 | + array_push($items, $this->getExceptionProperties($exception)); |
|
| 1708 | + if ($limit && (count($items) == $limit)) { |
|
| 1709 | + break; |
|
| 1710 | + } |
|
| 1711 | + } |
|
| 1712 | + |
|
| 1713 | + uasort($items, [$this, "sortStarttime"]); |
|
| 1714 | + |
|
| 1715 | + return $items; |
|
| 1716 | + } |
|
| 1717 | + |
|
| 1718 | + // From here on, the dates of the occurrences are calculated in local time, so the days we're looking |
|
| 1719 | + // at are calculated from the local time dates of $start and $end |
|
| 1720 | + // TODO use one isset |
|
| 1721 | + if (isset($this->recur['regen'], $this->action['datecompleted']) && $this->recur['regen']) { |
|
| 1722 | + $daystart = $this->dayStartOf($this->action['datecompleted']); |
|
| 1723 | + } |
|
| 1724 | + else { |
|
| 1725 | + $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence |
|
| 1726 | + } |
|
| 1727 | + |
|
| 1728 | + // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view |
|
| 1729 | + // or the end of the recurrence, whichever comes first |
|
| 1730 | + if ($end > $this->toGMT($this->tz, $this->recur["end"])) { |
|
| 1731 | + $rangeend = $this->toGMT($this->tz, $this->recur["end"]); |
|
| 1732 | + } |
|
| 1733 | + else { |
|
| 1734 | + $rangeend = $end; |
|
| 1735 | + } |
|
| 1736 | + |
|
| 1737 | + $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend)); |
|
| 1738 | + |
|
| 1739 | + // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range. |
|
| 1740 | + |
|
| 1741 | + switch ($this->recur["type"]) { |
|
| 1742 | + case 10: |
|
| 1743 | + // Daily |
|
| 1744 | + if ($this->recur["everyn"] <= 0) { |
|
| 1745 | + $this->recur["everyn"] = 1440; |
|
| 1746 | + } |
|
| 1747 | + |
|
| 1748 | + if ($this->recur["subtype"] == 0) { |
|
| 1749 | + // Every Nth day |
|
| 1750 | + for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) { |
|
| 1751 | + $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1752 | + } |
|
| 1753 | + } |
|
| 1754 | + else { |
|
| 1755 | + // Every workday |
|
| 1756 | + for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) { |
|
| 1757 | + $nowtime = $this->gmtime($now); |
|
| 1758 | + if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace |
|
| 1759 | + $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1760 | + } |
|
| 1761 | + } |
|
| 1762 | + } |
|
| 1763 | + |
|
| 1764 | + break; |
|
| 1765 | + |
|
| 1766 | + case 11: |
|
| 1767 | + // Weekly |
|
| 1768 | + if ($this->recur["everyn"] <= 0) { |
|
| 1769 | + $this->recur["everyn"] = 1; |
|
| 1770 | + } |
|
| 1771 | + |
|
| 1772 | + // If sliding flag is set then move to 'n' weeks |
|
| 1773 | + if ($this->recur['regen']) { |
|
| 1774 | + $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]); |
|
| 1775 | + } |
|
| 1776 | + |
|
| 1777 | + for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) { |
|
| 1778 | + if ($this->recur['regen']) { |
|
| 1779 | + $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1780 | + } |
|
| 1781 | + else { |
|
| 1782 | + // Loop through the whole following week to the first occurrence of the week, add each day that is specified |
|
| 1783 | + for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1784 | + $daynow = $now + $wday * 60 * 60 * 24; |
|
| 1785 | + // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item |
|
| 1786 | + if ($daynow <= $dayend) { |
|
| 1787 | + $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1788 | + if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ? |
|
| 1789 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1790 | + } |
|
| 1791 | + } |
|
| 1792 | + } |
|
| 1793 | + } |
|
| 1794 | + } |
|
| 1795 | + |
|
| 1796 | + break; |
|
| 1797 | + |
|
| 1798 | + case 12: |
|
| 1799 | + // Monthly |
|
| 1800 | + if ($this->recur["everyn"] <= 0) { |
|
| 1801 | + $this->recur["everyn"] = 1; |
|
| 1802 | + } |
|
| 1803 | + |
|
| 1804 | + // Loop through all months from start to end of occurrence, starting at beginning of first month |
|
| 1805 | + for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1806 | + if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months |
|
| 1807 | + $difference = 1; |
|
| 1808 | + if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) { |
|
| 1809 | + $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1; |
|
| 1810 | + } |
|
| 1811 | + $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60); |
|
| 1812 | + // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item |
|
| 1813 | + if ($daynow <= $dayend) { |
|
| 1814 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1815 | + } |
|
| 1816 | + } |
|
| 1817 | + elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months |
|
| 1818 | + // Sanitize input |
|
| 1819 | + if ($this->recur["weekdays"] == 0) { |
|
| 1820 | + $this->recur["weekdays"] = 1; |
|
| 1821 | + } |
|
| 1822 | + |
|
| 1823 | + // If nday is not set to the last day in the month |
|
| 1824 | + if ($this->recur["nday"] < 5) { |
|
| 1825 | + // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched |
|
| 1826 | + $ndaycounter = 0; |
|
| 1827 | + // Find matching weekday in this month |
|
| 1828 | + for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) { |
|
| 1829 | + $daynow = $now + $day * 60 * 60 * 24; |
|
| 1830 | + $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1831 | + |
|
| 1832 | + if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1833 | + ++$ndaycounter; |
|
| 1834 | + } |
|
| 1835 | + // check the selected pattern is same as asked Nth weekday,If so set the firstday |
|
| 1836 | + if ($this->recur["nday"] == $ndaycounter) { |
|
| 1837 | + $firstday = $day; |
|
| 1838 | + |
|
| 1839 | + break; |
|
| 1840 | + } |
|
| 1841 | + } |
|
| 1842 | + // $firstday is the day of the month on which the asked pattern of nth weekday matches |
|
| 1843 | + $daynow = $now + $firstday * 60 * 60 * 24; |
|
| 1844 | + } |
|
| 1845 | + else { |
|
| 1846 | + // Find last day in the month ($now is the firstday of the month) |
|
| 1847 | + $NumDaysInMonth = $this->daysInMonth($now, 1); |
|
| 1848 | + $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60); |
|
| 1849 | + |
|
| 1850 | + $nowtime = $this->gmtime($daynow); |
|
| 1851 | + while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) { |
|
| 1852 | + $daynow -= 86400; |
|
| 1853 | + $nowtime = $this->gmtime($daynow); |
|
| 1854 | + } |
|
| 1855 | + } |
|
| 1856 | + |
|
| 1857 | + /* |
|
| 1858 | 1858 | * checks weather the next coming day in recurrence pattern is less than or equal to end day of the * recurring item.Also check weather the coming day in recurrence pattern is greater than or equal to start * of recurring pattern, so that appointment that fall under the recurrence range are only displayed. |
| 1859 | 1859 | */ |
| 1860 | - if ($daynow <= $dayend && $daynow >= $daystart) { |
|
| 1861 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1862 | - } |
|
| 1863 | - } |
|
| 1864 | - elseif ($this->recur['regen']) { |
|
| 1865 | - $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60); |
|
| 1866 | - $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60); |
|
| 1867 | - |
|
| 1868 | - if ($now <= $dayend) { |
|
| 1869 | - $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1870 | - } |
|
| 1871 | - } |
|
| 1872 | - } |
|
| 1873 | - |
|
| 1874 | - break; |
|
| 1875 | - |
|
| 1876 | - case 13: |
|
| 1877 | - // Yearly |
|
| 1878 | - if ($this->recur["everyn"] <= 0) { |
|
| 1879 | - $this->recur["everyn"] = 12; |
|
| 1880 | - } |
|
| 1881 | - |
|
| 1882 | - for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1883 | - if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month |
|
| 1884 | - // recur["month"] is in minutes since the beginning of the year |
|
| 1885 | - $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11] |
|
| 1886 | - $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31] |
|
| 1887 | - $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month |
|
| 1888 | - if ($monthday > $this->daysInMonth($monthstart, 1)) { |
|
| 1889 | - $monthday = $this->daysInMonth($monthstart, 1); |
|
| 1890 | - } // Cap $monthday on month length (eg 28 feb instead of 29 feb) |
|
| 1891 | - $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60; |
|
| 1892 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1893 | - } |
|
| 1894 | - elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
|
| 1895 | - // Go the correct month |
|
| 1896 | - $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60; |
|
| 1897 | - |
|
| 1898 | - // Find first matching weekday in this month |
|
| 1899 | - for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1900 | - $daynow = $monthnow + $wday * 60 * 60 * 24; |
|
| 1901 | - $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1902 | - |
|
| 1903 | - if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1904 | - $firstday = $wday; |
|
| 1905 | - |
|
| 1906 | - break; |
|
| 1907 | - } |
|
| 1908 | - } |
|
| 1909 | - |
|
| 1910 | - // Same as above (monthly) |
|
| 1911 | - $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24; |
|
| 1912 | - |
|
| 1913 | - while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) { |
|
| 1914 | - $daynow -= 7 * 60 * 60 * 24; |
|
| 1915 | - } |
|
| 1916 | - |
|
| 1917 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1918 | - } |
|
| 1919 | - elseif ($this->recur['regen']) { |
|
| 1920 | - $year_starttime = $this->gmtime($now); |
|
| 1921 | - $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
|
| 1922 | - $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */); |
|
| 1923 | - |
|
| 1924 | - if ($now <= $dayend) { |
|
| 1925 | - $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1926 | - } |
|
| 1927 | - } |
|
| 1928 | - } |
|
| 1929 | - } |
|
| 1930 | - // to get all exception items |
|
| 1931 | - if (!empty($this->recur['changed_occurrences'])) { |
|
| 1932 | - $this->processExceptionItems($items, $start, $end); |
|
| 1933 | - } |
|
| 1934 | - } |
|
| 1935 | - |
|
| 1936 | - // sort items on starttime |
|
| 1937 | - usort($items, [$this, "sortStarttime"]); |
|
| 1938 | - |
|
| 1939 | - // Return the MAPI-compatible list of items for this object |
|
| 1940 | - return $items; |
|
| 1941 | - } |
|
| 1942 | - |
|
| 1943 | - public function sortStarttime($a, $b) { |
|
| 1944 | - $aTime = $a[$this->proptags["startdate"]]; |
|
| 1945 | - $bTime = $b[$this->proptags["startdate"]]; |
|
| 1946 | - |
|
| 1947 | - return $aTime == $bTime ? 0 : ($aTime > $bTime ? 1 : -1); |
|
| 1948 | - } |
|
| 1949 | - |
|
| 1950 | - /** |
|
| 1951 | - * daysInMonth. |
|
| 1952 | - * |
|
| 1953 | - * Returns the number of days in the upcoming number of months. If you specify 1 month as |
|
| 1954 | - * $months it will give you the number of days in the month of $date. If you specify more it |
|
| 1955 | - * will also count the days in the upcoming months and add that to the number of days. So |
|
| 1956 | - * if you have a date in march and you specify $months as 2 it will return 61. |
|
| 1957 | - * |
|
| 1958 | - * @param int $date specified date as timestamp from which you want to know the number |
|
| 1959 | - * of days in the month |
|
| 1960 | - * @param int $months number of months you want to know the number of days in |
|
| 1961 | - * @returns Integer Number of days in the specified amount of months. |
|
| 1962 | - */ |
|
| 1963 | - public function daysInMonth($date, $months) { |
|
| 1964 | - $days = 0; |
|
| 1965 | - |
|
| 1966 | - for ($i = 0; $i < $months; ++$i) { |
|
| 1967 | - $days += date("t", $date + $days * 24 * 60 * 60); |
|
| 1968 | - } |
|
| 1969 | - |
|
| 1970 | - return $days; |
|
| 1971 | - } |
|
| 1972 | - |
|
| 1973 | - // Converts MAPI-style 'minutes' into the month of the year [0..11] |
|
| 1974 | - public function monthOfYear($minutes) { |
|
| 1975 | - $d = gmmktime(0, 0, 0, 1, 1, 2001); // The year 2001 was a non-leap year, and the minutes provided are always in non-leap-year-minutes |
|
| 1976 | - |
|
| 1977 | - $d += $minutes * 60; |
|
| 1978 | - |
|
| 1979 | - $dtime = $this->gmtime($d); |
|
| 1980 | - |
|
| 1981 | - return $dtime["tm_mon"]; |
|
| 1982 | - } |
|
| 1983 | - |
|
| 1984 | - public function sortExceptionStart($a, $b) { |
|
| 1985 | - return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1); |
|
| 1986 | - } |
|
| 1987 | - } |
|
| 1860 | + if ($daynow <= $dayend && $daynow >= $daystart) { |
|
| 1861 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1862 | + } |
|
| 1863 | + } |
|
| 1864 | + elseif ($this->recur['regen']) { |
|
| 1865 | + $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60); |
|
| 1866 | + $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60); |
|
| 1867 | + |
|
| 1868 | + if ($now <= $dayend) { |
|
| 1869 | + $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1870 | + } |
|
| 1871 | + } |
|
| 1872 | + } |
|
| 1873 | + |
|
| 1874 | + break; |
|
| 1875 | + |
|
| 1876 | + case 13: |
|
| 1877 | + // Yearly |
|
| 1878 | + if ($this->recur["everyn"] <= 0) { |
|
| 1879 | + $this->recur["everyn"] = 12; |
|
| 1880 | + } |
|
| 1881 | + |
|
| 1882 | + for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1883 | + if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month |
|
| 1884 | + // recur["month"] is in minutes since the beginning of the year |
|
| 1885 | + $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11] |
|
| 1886 | + $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31] |
|
| 1887 | + $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month |
|
| 1888 | + if ($monthday > $this->daysInMonth($monthstart, 1)) { |
|
| 1889 | + $monthday = $this->daysInMonth($monthstart, 1); |
|
| 1890 | + } // Cap $monthday on month length (eg 28 feb instead of 29 feb) |
|
| 1891 | + $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60; |
|
| 1892 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1893 | + } |
|
| 1894 | + elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
|
| 1895 | + // Go the correct month |
|
| 1896 | + $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60; |
|
| 1897 | + |
|
| 1898 | + // Find first matching weekday in this month |
|
| 1899 | + for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1900 | + $daynow = $monthnow + $wday * 60 * 60 * 24; |
|
| 1901 | + $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1902 | + |
|
| 1903 | + if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1904 | + $firstday = $wday; |
|
| 1905 | + |
|
| 1906 | + break; |
|
| 1907 | + } |
|
| 1908 | + } |
|
| 1909 | + |
|
| 1910 | + // Same as above (monthly) |
|
| 1911 | + $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24; |
|
| 1912 | + |
|
| 1913 | + while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) { |
|
| 1914 | + $daynow -= 7 * 60 * 60 * 24; |
|
| 1915 | + } |
|
| 1916 | + |
|
| 1917 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1918 | + } |
|
| 1919 | + elseif ($this->recur['regen']) { |
|
| 1920 | + $year_starttime = $this->gmtime($now); |
|
| 1921 | + $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
|
| 1922 | + $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */); |
|
| 1923 | + |
|
| 1924 | + if ($now <= $dayend) { |
|
| 1925 | + $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1926 | + } |
|
| 1927 | + } |
|
| 1928 | + } |
|
| 1929 | + } |
|
| 1930 | + // to get all exception items |
|
| 1931 | + if (!empty($this->recur['changed_occurrences'])) { |
|
| 1932 | + $this->processExceptionItems($items, $start, $end); |
|
| 1933 | + } |
|
| 1934 | + } |
|
| 1935 | + |
|
| 1936 | + // sort items on starttime |
|
| 1937 | + usort($items, [$this, "sortStarttime"]); |
|
| 1938 | + |
|
| 1939 | + // Return the MAPI-compatible list of items for this object |
|
| 1940 | + return $items; |
|
| 1941 | + } |
|
| 1942 | + |
|
| 1943 | + public function sortStarttime($a, $b) { |
|
| 1944 | + $aTime = $a[$this->proptags["startdate"]]; |
|
| 1945 | + $bTime = $b[$this->proptags["startdate"]]; |
|
| 1946 | + |
|
| 1947 | + return $aTime == $bTime ? 0 : ($aTime > $bTime ? 1 : -1); |
|
| 1948 | + } |
|
| 1949 | + |
|
| 1950 | + /** |
|
| 1951 | + * daysInMonth. |
|
| 1952 | + * |
|
| 1953 | + * Returns the number of days in the upcoming number of months. If you specify 1 month as |
|
| 1954 | + * $months it will give you the number of days in the month of $date. If you specify more it |
|
| 1955 | + * will also count the days in the upcoming months and add that to the number of days. So |
|
| 1956 | + * if you have a date in march and you specify $months as 2 it will return 61. |
|
| 1957 | + * |
|
| 1958 | + * @param int $date specified date as timestamp from which you want to know the number |
|
| 1959 | + * of days in the month |
|
| 1960 | + * @param int $months number of months you want to know the number of days in |
|
| 1961 | + * @returns Integer Number of days in the specified amount of months. |
|
| 1962 | + */ |
|
| 1963 | + public function daysInMonth($date, $months) { |
|
| 1964 | + $days = 0; |
|
| 1965 | + |
|
| 1966 | + for ($i = 0; $i < $months; ++$i) { |
|
| 1967 | + $days += date("t", $date + $days * 24 * 60 * 60); |
|
| 1968 | + } |
|
| 1969 | + |
|
| 1970 | + return $days; |
|
| 1971 | + } |
|
| 1972 | + |
|
| 1973 | + // Converts MAPI-style 'minutes' into the month of the year [0..11] |
|
| 1974 | + public function monthOfYear($minutes) { |
|
| 1975 | + $d = gmmktime(0, 0, 0, 1, 1, 2001); // The year 2001 was a non-leap year, and the minutes provided are always in non-leap-year-minutes |
|
| 1976 | + |
|
| 1977 | + $d += $minutes * 60; |
|
| 1978 | + |
|
| 1979 | + $dtime = $this->gmtime($d); |
|
| 1980 | + |
|
| 1981 | + return $dtime["tm_mon"]; |
|
| 1982 | + } |
|
| 1983 | + |
|
| 1984 | + public function sortExceptionStart($a, $b) { |
|
| 1985 | + return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1); |
|
| 1986 | + } |
|
| 1987 | + } |
|
@@ -1739,193 +1739,193 @@ |
||
| 1739 | 1739 | // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range. |
| 1740 | 1740 | |
| 1741 | 1741 | switch ($this->recur["type"]) { |
| 1742 | - case 10: |
|
| 1743 | - // Daily |
|
| 1744 | - if ($this->recur["everyn"] <= 0) { |
|
| 1745 | - $this->recur["everyn"] = 1440; |
|
| 1746 | - } |
|
| 1747 | - |
|
| 1748 | - if ($this->recur["subtype"] == 0) { |
|
| 1749 | - // Every Nth day |
|
| 1750 | - for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) { |
|
| 1751 | - $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1752 | - } |
|
| 1753 | - } |
|
| 1754 | - else { |
|
| 1755 | - // Every workday |
|
| 1756 | - for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) { |
|
| 1757 | - $nowtime = $this->gmtime($now); |
|
| 1758 | - if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace |
|
| 1759 | - $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1760 | - } |
|
| 1761 | - } |
|
| 1762 | - } |
|
| 1763 | - |
|
| 1764 | - break; |
|
| 1765 | - |
|
| 1766 | - case 11: |
|
| 1767 | - // Weekly |
|
| 1768 | - if ($this->recur["everyn"] <= 0) { |
|
| 1769 | - $this->recur["everyn"] = 1; |
|
| 1770 | - } |
|
| 1771 | - |
|
| 1772 | - // If sliding flag is set then move to 'n' weeks |
|
| 1773 | - if ($this->recur['regen']) { |
|
| 1774 | - $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]); |
|
| 1775 | - } |
|
| 1776 | - |
|
| 1777 | - for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) { |
|
| 1778 | - if ($this->recur['regen']) { |
|
| 1779 | - $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1780 | - } |
|
| 1781 | - else { |
|
| 1782 | - // Loop through the whole following week to the first occurrence of the week, add each day that is specified |
|
| 1783 | - for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1784 | - $daynow = $now + $wday * 60 * 60 * 24; |
|
| 1785 | - // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item |
|
| 1786 | - if ($daynow <= $dayend) { |
|
| 1787 | - $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1788 | - if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ? |
|
| 1789 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1790 | - } |
|
| 1791 | - } |
|
| 1792 | - } |
|
| 1793 | - } |
|
| 1794 | - } |
|
| 1795 | - |
|
| 1796 | - break; |
|
| 1797 | - |
|
| 1798 | - case 12: |
|
| 1799 | - // Monthly |
|
| 1800 | - if ($this->recur["everyn"] <= 0) { |
|
| 1801 | - $this->recur["everyn"] = 1; |
|
| 1802 | - } |
|
| 1803 | - |
|
| 1804 | - // Loop through all months from start to end of occurrence, starting at beginning of first month |
|
| 1805 | - for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1806 | - if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months |
|
| 1807 | - $difference = 1; |
|
| 1808 | - if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) { |
|
| 1809 | - $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1; |
|
| 1810 | - } |
|
| 1811 | - $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60); |
|
| 1812 | - // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item |
|
| 1813 | - if ($daynow <= $dayend) { |
|
| 1814 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1815 | - } |
|
| 1816 | - } |
|
| 1817 | - elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months |
|
| 1818 | - // Sanitize input |
|
| 1819 | - if ($this->recur["weekdays"] == 0) { |
|
| 1820 | - $this->recur["weekdays"] = 1; |
|
| 1821 | - } |
|
| 1822 | - |
|
| 1823 | - // If nday is not set to the last day in the month |
|
| 1824 | - if ($this->recur["nday"] < 5) { |
|
| 1825 | - // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched |
|
| 1826 | - $ndaycounter = 0; |
|
| 1827 | - // Find matching weekday in this month |
|
| 1828 | - for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) { |
|
| 1829 | - $daynow = $now + $day * 60 * 60 * 24; |
|
| 1830 | - $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1831 | - |
|
| 1832 | - if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1833 | - ++$ndaycounter; |
|
| 1834 | - } |
|
| 1835 | - // check the selected pattern is same as asked Nth weekday,If so set the firstday |
|
| 1836 | - if ($this->recur["nday"] == $ndaycounter) { |
|
| 1837 | - $firstday = $day; |
|
| 1838 | - |
|
| 1839 | - break; |
|
| 1840 | - } |
|
| 1841 | - } |
|
| 1842 | - // $firstday is the day of the month on which the asked pattern of nth weekday matches |
|
| 1843 | - $daynow = $now + $firstday * 60 * 60 * 24; |
|
| 1844 | - } |
|
| 1845 | - else { |
|
| 1846 | - // Find last day in the month ($now is the firstday of the month) |
|
| 1847 | - $NumDaysInMonth = $this->daysInMonth($now, 1); |
|
| 1848 | - $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60); |
|
| 1849 | - |
|
| 1850 | - $nowtime = $this->gmtime($daynow); |
|
| 1851 | - while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) { |
|
| 1852 | - $daynow -= 86400; |
|
| 1853 | - $nowtime = $this->gmtime($daynow); |
|
| 1854 | - } |
|
| 1855 | - } |
|
| 1856 | - |
|
| 1857 | - /* |
|
| 1742 | + case 10: |
|
| 1743 | + // Daily |
|
| 1744 | + if ($this->recur["everyn"] <= 0) { |
|
| 1745 | + $this->recur["everyn"] = 1440; |
|
| 1746 | + } |
|
| 1747 | + |
|
| 1748 | + if ($this->recur["subtype"] == 0) { |
|
| 1749 | + // Every Nth day |
|
| 1750 | + for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) { |
|
| 1751 | + $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1752 | + } |
|
| 1753 | + } |
|
| 1754 | + else { |
|
| 1755 | + // Every workday |
|
| 1756 | + for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) { |
|
| 1757 | + $nowtime = $this->gmtime($now); |
|
| 1758 | + if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace |
|
| 1759 | + $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1760 | + } |
|
| 1761 | + } |
|
| 1762 | + } |
|
| 1763 | + |
|
| 1764 | + break; |
|
| 1765 | + |
|
| 1766 | + case 11: |
|
| 1767 | + // Weekly |
|
| 1768 | + if ($this->recur["everyn"] <= 0) { |
|
| 1769 | + $this->recur["everyn"] = 1; |
|
| 1770 | + } |
|
| 1771 | + |
|
| 1772 | + // If sliding flag is set then move to 'n' weeks |
|
| 1773 | + if ($this->recur['regen']) { |
|
| 1774 | + $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]); |
|
| 1775 | + } |
|
| 1776 | + |
|
| 1777 | + for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) { |
|
| 1778 | + if ($this->recur['regen']) { |
|
| 1779 | + $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1780 | + } |
|
| 1781 | + else { |
|
| 1782 | + // Loop through the whole following week to the first occurrence of the week, add each day that is specified |
|
| 1783 | + for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1784 | + $daynow = $now + $wday * 60 * 60 * 24; |
|
| 1785 | + // checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item |
|
| 1786 | + if ($daynow <= $dayend) { |
|
| 1787 | + $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1788 | + if (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))) { // Selected ? |
|
| 1789 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1790 | + } |
|
| 1791 | + } |
|
| 1792 | + } |
|
| 1793 | + } |
|
| 1794 | + } |
|
| 1795 | + |
|
| 1796 | + break; |
|
| 1797 | + |
|
| 1798 | + case 12: |
|
| 1799 | + // Monthly |
|
| 1800 | + if ($this->recur["everyn"] <= 0) { |
|
| 1801 | + $this->recur["everyn"] = 1; |
|
| 1802 | + } |
|
| 1803 | + |
|
| 1804 | + // Loop through all months from start to end of occurrence, starting at beginning of first month |
|
| 1805 | + for ($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1806 | + if (isset($this->recur["monthday"]) && ($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months |
|
| 1807 | + $difference = 1; |
|
| 1808 | + if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) { |
|
| 1809 | + $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1; |
|
| 1810 | + } |
|
| 1811 | + $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60); |
|
| 1812 | + // checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item |
|
| 1813 | + if ($daynow <= $dayend) { |
|
| 1814 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1815 | + } |
|
| 1816 | + } |
|
| 1817 | + elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months |
|
| 1818 | + // Sanitize input |
|
| 1819 | + if ($this->recur["weekdays"] == 0) { |
|
| 1820 | + $this->recur["weekdays"] = 1; |
|
| 1821 | + } |
|
| 1822 | + |
|
| 1823 | + // If nday is not set to the last day in the month |
|
| 1824 | + if ($this->recur["nday"] < 5) { |
|
| 1825 | + // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched |
|
| 1826 | + $ndaycounter = 0; |
|
| 1827 | + // Find matching weekday in this month |
|
| 1828 | + for ($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; ++$day) { |
|
| 1829 | + $daynow = $now + $day * 60 * 60 * 24; |
|
| 1830 | + $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1831 | + |
|
| 1832 | + if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1833 | + ++$ndaycounter; |
|
| 1834 | + } |
|
| 1835 | + // check the selected pattern is same as asked Nth weekday,If so set the firstday |
|
| 1836 | + if ($this->recur["nday"] == $ndaycounter) { |
|
| 1837 | + $firstday = $day; |
|
| 1838 | + |
|
| 1839 | + break; |
|
| 1840 | + } |
|
| 1841 | + } |
|
| 1842 | + // $firstday is the day of the month on which the asked pattern of nth weekday matches |
|
| 1843 | + $daynow = $now + $firstday * 60 * 60 * 24; |
|
| 1844 | + } |
|
| 1845 | + else { |
|
| 1846 | + // Find last day in the month ($now is the firstday of the month) |
|
| 1847 | + $NumDaysInMonth = $this->daysInMonth($now, 1); |
|
| 1848 | + $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60); |
|
| 1849 | + |
|
| 1850 | + $nowtime = $this->gmtime($daynow); |
|
| 1851 | + while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) == 0) { |
|
| 1852 | + $daynow -= 86400; |
|
| 1853 | + $nowtime = $this->gmtime($daynow); |
|
| 1854 | + } |
|
| 1855 | + } |
|
| 1856 | + |
|
| 1857 | + /* |
|
| 1858 | 1858 | * checks weather the next coming day in recurrence pattern is less than or equal to end day of the * recurring item.Also check weather the coming day in recurrence pattern is greater than or equal to start * of recurring pattern, so that appointment that fall under the recurrence range are only displayed. |
| 1859 | 1859 | */ |
| 1860 | - if ($daynow <= $dayend && $daynow >= $daystart) { |
|
| 1861 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1862 | - } |
|
| 1863 | - } |
|
| 1864 | - elseif ($this->recur['regen']) { |
|
| 1865 | - $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60); |
|
| 1866 | - $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60); |
|
| 1867 | - |
|
| 1868 | - if ($now <= $dayend) { |
|
| 1869 | - $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1870 | - } |
|
| 1871 | - } |
|
| 1872 | - } |
|
| 1873 | - |
|
| 1874 | - break; |
|
| 1875 | - |
|
| 1876 | - case 13: |
|
| 1877 | - // Yearly |
|
| 1878 | - if ($this->recur["everyn"] <= 0) { |
|
| 1879 | - $this->recur["everyn"] = 12; |
|
| 1880 | - } |
|
| 1881 | - |
|
| 1882 | - for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1883 | - if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month |
|
| 1884 | - // recur["month"] is in minutes since the beginning of the year |
|
| 1885 | - $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11] |
|
| 1886 | - $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31] |
|
| 1887 | - $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month |
|
| 1888 | - if ($monthday > $this->daysInMonth($monthstart, 1)) { |
|
| 1889 | - $monthday = $this->daysInMonth($monthstart, 1); |
|
| 1890 | - } // Cap $monthday on month length (eg 28 feb instead of 29 feb) |
|
| 1891 | - $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60; |
|
| 1892 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1893 | - } |
|
| 1894 | - elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
|
| 1895 | - // Go the correct month |
|
| 1896 | - $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60; |
|
| 1897 | - |
|
| 1898 | - // Find first matching weekday in this month |
|
| 1899 | - for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1900 | - $daynow = $monthnow + $wday * 60 * 60 * 24; |
|
| 1901 | - $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1902 | - |
|
| 1903 | - if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1904 | - $firstday = $wday; |
|
| 1905 | - |
|
| 1906 | - break; |
|
| 1907 | - } |
|
| 1908 | - } |
|
| 1909 | - |
|
| 1910 | - // Same as above (monthly) |
|
| 1911 | - $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24; |
|
| 1912 | - |
|
| 1913 | - while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) { |
|
| 1914 | - $daynow -= 7 * 60 * 60 * 24; |
|
| 1915 | - } |
|
| 1916 | - |
|
| 1917 | - $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1918 | - } |
|
| 1919 | - elseif ($this->recur['regen']) { |
|
| 1920 | - $year_starttime = $this->gmtime($now); |
|
| 1921 | - $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
|
| 1922 | - $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */); |
|
| 1923 | - |
|
| 1924 | - if ($now <= $dayend) { |
|
| 1925 | - $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1926 | - } |
|
| 1927 | - } |
|
| 1928 | - } |
|
| 1860 | + if ($daynow <= $dayend && $daynow >= $daystart) { |
|
| 1861 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1862 | + } |
|
| 1863 | + } |
|
| 1864 | + elseif ($this->recur['regen']) { |
|
| 1865 | + $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60); |
|
| 1866 | + $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60); |
|
| 1867 | + |
|
| 1868 | + if ($now <= $dayend) { |
|
| 1869 | + $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1870 | + } |
|
| 1871 | + } |
|
| 1872 | + } |
|
| 1873 | + |
|
| 1874 | + break; |
|
| 1875 | + |
|
| 1876 | + case 13: |
|
| 1877 | + // Yearly |
|
| 1878 | + if ($this->recur["everyn"] <= 0) { |
|
| 1879 | + $this->recur["everyn"] = 12; |
|
| 1880 | + } |
|
| 1881 | + |
|
| 1882 | + for ($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60) { |
|
| 1883 | + if (isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month |
|
| 1884 | + // recur["month"] is in minutes since the beginning of the year |
|
| 1885 | + $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11] |
|
| 1886 | + $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31] |
|
| 1887 | + $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month |
|
| 1888 | + if ($monthday > $this->daysInMonth($monthstart, 1)) { |
|
| 1889 | + $monthday = $this->daysInMonth($monthstart, 1); |
|
| 1890 | + } // Cap $monthday on month length (eg 28 feb instead of 29 feb) |
|
| 1891 | + $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60; |
|
| 1892 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1893 | + } |
|
| 1894 | + elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
|
| 1895 | + // Go the correct month |
|
| 1896 | + $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60; |
|
| 1897 | + |
|
| 1898 | + // Find first matching weekday in this month |
|
| 1899 | + for ($wday = 0; $wday < 7; ++$wday) { |
|
| 1900 | + $daynow = $monthnow + $wday * 60 * 60 * 24; |
|
| 1901 | + $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
|
| 1902 | + |
|
| 1903 | + if ($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
|
| 1904 | + $firstday = $wday; |
|
| 1905 | + |
|
| 1906 | + break; |
|
| 1907 | + } |
|
| 1908 | + } |
|
| 1909 | + |
|
| 1910 | + // Same as above (monthly) |
|
| 1911 | + $daynow = $monthnow + ($firstday + ($this->recur["nday"] - 1) * 7) * 60 * 60 * 24; |
|
| 1912 | + |
|
| 1913 | + while ($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) { |
|
| 1914 | + $daynow -= 7 * 60 * 60 * 24; |
|
| 1915 | + } |
|
| 1916 | + |
|
| 1917 | + $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1918 | + } |
|
| 1919 | + elseif ($this->recur['regen']) { |
|
| 1920 | + $year_starttime = $this->gmtime($now); |
|
| 1921 | + $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
|
| 1922 | + $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */); |
|
| 1923 | + |
|
| 1924 | + if ($now <= $dayend) { |
|
| 1925 | + $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
|
| 1926 | + } |
|
| 1927 | + } |
|
| 1928 | + } |
|
| 1929 | 1929 | } |
| 1930 | 1930 | // to get all exception items |
| 1931 | 1931 | if (!empty($this->recur['changed_occurrences'])) { |
@@ -573,14 +573,14 @@ discard block |
||
| 573 | 573 | return; |
| 574 | 574 | } |
| 575 | 575 | |
| 576 | - $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]); |
|
| 576 | + $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int)$this->recur["type"], 0x20, (int)$this->recur["subtype"]); |
|
| 577 | 577 | |
| 578 | 578 | $weekstart = 1; // monday |
| 579 | 579 | $forwardcount = 0; |
| 580 | 580 | $restocc = 0; |
| 581 | - $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday) |
|
| 581 | + $dayofweek = (int)gmdate("w", (int)$this->recur["start"]); // 0 (for Sunday) through 6 (for Saturday) |
|
| 582 | 582 | |
| 583 | - $term = (int) $this->recur["type"]; |
|
| 583 | + $term = (int)$this->recur["type"]; |
|
| 584 | 584 | |
| 585 | 585 | switch ($term) { |
| 586 | 586 | case 0x0A: |
@@ -596,12 +596,12 @@ discard block |
||
| 596 | 596 | else { |
| 597 | 597 | // Daily every N days (everyN in minutes) |
| 598 | 598 | |
| 599 | - $everyn = ((int) $this->recur["everyn"]) / 1440; |
|
| 599 | + $everyn = ((int)$this->recur["everyn"]) / 1440; |
|
| 600 | 600 | |
| 601 | 601 | // Calc first occ |
| 602 | - $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]); |
|
| 602 | + $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int)$this->recur["everyn"]); |
|
| 603 | 603 | |
| 604 | - $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0); |
|
| 604 | + $rdata .= pack("VVV", $firstocc, (int)$this->recur["everyn"], $this->recur["regen"] ? 1 : 0); |
|
| 605 | 605 | } |
| 606 | 606 | |
| 607 | 607 | break; |
@@ -624,7 +624,7 @@ discard block |
||
| 624 | 624 | $daycount = 0; |
| 625 | 625 | $dayskip = -1; |
| 626 | 626 | for ($j = 0; $j < 7; ++$j) { |
| 627 | - if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 627 | + if (((int)$this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 628 | 628 | if ($dayskip == -1) { |
| 629 | 629 | $dayskip = $j; |
| 630 | 630 | } |
@@ -643,35 +643,35 @@ discard block |
||
| 643 | 643 | |
| 644 | 644 | // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
| 645 | 645 | // remaining occurrences based on the start of the recurrence. |
| 646 | - if (((int) $this->recur["term"]) == 0x22) { |
|
| 646 | + if (((int)$this->recur["term"]) == 0x22) { |
|
| 647 | 647 | // $weekskip is the amount of weeks to skip from the startdate before the first occurrence |
| 648 | 648 | // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that |
| 649 | 649 | // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over |
| 650 | 650 | // (eg when numoccur = 2, and daycount = 1) |
| 651 | - $forwardcount = floor((int) ($this->recur["numoccur"] - 1) / $daycount); |
|
| 651 | + $forwardcount = floor((int)($this->recur["numoccur"] - 1) / $daycount); |
|
| 652 | 652 | |
| 653 | 653 | // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one |
| 654 | 654 | // for the occurrence on the first day |
| 655 | - $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount * $daycount) - 1; |
|
| 655 | + $restocc = ((int)$this->recur["numoccur"]) - ($forwardcount * $daycount) - 1; |
|
| 656 | 656 | |
| 657 | 657 | // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence |
| 658 | - $forwardcount *= (int) $this->recur["everyn"]; |
|
| 658 | + $forwardcount *= (int)$this->recur["everyn"]; |
|
| 659 | 659 | } |
| 660 | 660 | |
| 661 | 661 | // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week) |
| 662 | - $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60); |
|
| 662 | + $this->recur["start"] = ((int)$this->recur["start"]) + ($dayskip * 24 * 60 * 60) + ($weekskip * (((int)$this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60); |
|
| 663 | 663 | } |
| 664 | 664 | |
| 665 | 665 | // Calc first occ |
| 666 | - $firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int) $this->recur["everyn"]) * 7 * 24 * 60); |
|
| 666 | + $firstocc = ($this->unixDataToRecurData($this->recur["start"])) % (((int)$this->recur["everyn"]) * 7 * 24 * 60); |
|
| 667 | 667 | |
| 668 | - $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60; |
|
| 668 | + $firstocc -= (((int)gmdate("w", (int)$this->recur["start"])) - 1) * 24 * 60; |
|
| 669 | 669 | |
| 670 | 670 | if ($this->recur["regen"]) { |
| 671 | - $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1); |
|
| 671 | + $rdata .= pack("VVV", $firstocc, (int)$this->recur["everyn"], 1); |
|
| 672 | 672 | } |
| 673 | 673 | else { |
| 674 | - $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]); |
|
| 674 | + $rdata .= pack("VVVV", $firstocc, (int)$this->recur["everyn"], 0, (int)$this->recur["weekdays"]); |
|
| 675 | 675 | } |
| 676 | 676 | |
| 677 | 677 | break; |
@@ -688,31 +688,31 @@ discard block |
||
| 688 | 688 | } |
| 689 | 689 | |
| 690 | 690 | if ($term == 0x0C /* monthly */) { |
| 691 | - $everyn = (int) $this->recur["everyn"]; |
|
| 691 | + $everyn = (int)$this->recur["everyn"]; |
|
| 692 | 692 | } |
| 693 | 693 | else { |
| 694 | - $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12; |
|
| 694 | + $everyn = $this->recur["regen"] ? ((int)$this->recur["everyn"]) * 12 : 12; |
|
| 695 | 695 | } |
| 696 | 696 | |
| 697 | 697 | // Get montday/month/year of original start |
| 698 | - $curmonthday = gmdate("j", (int) $this->recur["start"]); |
|
| 699 | - $curyear = gmdate("Y", (int) $this->recur["start"]); |
|
| 700 | - $curmonth = gmdate("n", (int) $this->recur["start"]); |
|
| 698 | + $curmonthday = gmdate("j", (int)$this->recur["start"]); |
|
| 699 | + $curyear = gmdate("Y", (int)$this->recur["start"]); |
|
| 700 | + $curmonth = gmdate("n", (int)$this->recur["start"]); |
|
| 701 | 701 | |
| 702 | 702 | // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
| 703 | 703 | // remaining occurrences based on the start of the recurrence. |
| 704 | - if (((int) $this->recur["term"]) == 0x22) { |
|
| 704 | + if (((int)$this->recur["term"]) == 0x22) { |
|
| 705 | 705 | // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus |
| 706 | 706 | // one to make sure there are always at least one occurrence left) |
| 707 | - $forwardcount = ((((int) $this->recur["numoccur"]) - 1) * $everyn); |
|
| 707 | + $forwardcount = ((((int)$this->recur["numoccur"]) - 1) * $everyn); |
|
| 708 | 708 | } |
| 709 | 709 | |
| 710 | 710 | // Get month for yearly on D'th day of month M |
| 711 | 711 | if ($term == 0x0D /* yearly */) { |
| 712 | - $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg |
|
| 712 | + $selmonth = floor(((int)$this->recur["month"]) / (24 * 60 * 29)) + 1; // 1=jan, 2=feb, eg |
|
| 713 | 713 | } |
| 714 | 714 | |
| 715 | - switch ((int) $this->recur["subtype"]) { |
|
| 715 | + switch ((int)$this->recur["subtype"]) { |
|
| 716 | 716 | // on D day of every M month |
| 717 | 717 | case 2: |
| 718 | 718 | if (!isset($this->recur["monthday"])) { |
@@ -725,11 +725,11 @@ discard block |
||
| 725 | 725 | // Go the beginning of the month |
| 726 | 726 | $this->recur["start"] -= ($curmonthday - 1) * 24 * 60 * 60; |
| 727 | 727 | // Go the the correct month day |
| 728 | - $this->recur["start"] += (((int) $this->recur["monthday"]) - 1) * 24 * 60 * 60; |
|
| 728 | + $this->recur["start"] += (((int)$this->recur["monthday"]) - 1) * 24 * 60 * 60; |
|
| 729 | 729 | |
| 730 | 730 | // If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence |
| 731 | - if (($term == 0x0C /* monthly */ && ((int) $this->recur["monthday"]) < $curmonthday) || |
|
| 732 | - ($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)))) { |
|
| 731 | + if (($term == 0x0C /* monthly */ && ((int)$this->recur["monthday"]) < $curmonthday) || |
|
| 732 | + ($term == 0x0D /* yearly */ && ($selmonth != $curmonth || ($selmonth == $curmonth && ((int)$this->recur["monthday"]) < $curmonthday)))) { |
|
| 733 | 733 | if ($term == 0x0D /* yearly */) { |
| 734 | 734 | if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date |
| 735 | 735 | $count = $everyn - ($curmonth - $selmonth); |
@@ -739,7 +739,7 @@ discard block |
||
| 739 | 739 | } |
| 740 | 740 | else { |
| 741 | 741 | // Go to next occurrence while recurrence start date is greater than occurrence date but within same month |
| 742 | - if (((int) $this->recur["monthday"]) < $curmonthday) { |
|
| 742 | + if (((int)$this->recur["monthday"]) < $curmonthday) { |
|
| 743 | 743 | $count = $everyn; |
| 744 | 744 | } |
| 745 | 745 | } |
@@ -765,33 +765,33 @@ discard block |
||
| 765 | 765 | // of each month will overshoot in february (29 days). We compensate for that by checking |
| 766 | 766 | // if the day of the month we got is wrong, and then back up to the last day of the previous |
| 767 | 767 | // month. |
| 768 | - if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 && |
|
| 769 | - gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) { |
|
| 770 | - $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 * 60; |
|
| 768 | + if (((int)$this->recur["monthday"]) >= 28 && ((int)$this->recur["monthday"]) <= 31 && |
|
| 769 | + gmdate("j", ((int)$this->recur["start"])) < ((int)$this->recur["monthday"])) { |
|
| 770 | + $this->recur["start"] -= gmdate("j", ((int)$this->recur["start"])) * 24 * 60 * 60; |
|
| 771 | 771 | } |
| 772 | 772 | |
| 773 | 773 | // "start" is now the first occurrence |
| 774 | 774 | |
| 775 | 775 | if ($term == 0x0C /* monthly */) { |
| 776 | 776 | // Calc first occ |
| 777 | - $monthIndex = ((((12 % $everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 777 | + $monthIndex = ((((12 % $everyn) * ((((int)gmdate("Y", $this->recur["start"])) - 1601) % $everyn)) % $everyn) + (((int)gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 778 | 778 | |
| 779 | 779 | $firstocc = 0; |
| 780 | 780 | for ($i = 0; $i < $monthIndex; ++$i) { |
| 781 | 781 | $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60; |
| 782 | 782 | } |
| 783 | 783 | |
| 784 | - $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
|
| 784 | + $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int)$this->recur["monthday"]); |
|
| 785 | 785 | } |
| 786 | 786 | else { |
| 787 | 787 | // Calc first occ |
| 788 | 788 | $firstocc = 0; |
| 789 | - $monthIndex = (int) gmdate("n", $this->recur["start"]); |
|
| 789 | + $monthIndex = (int)gmdate("n", $this->recur["start"]); |
|
| 790 | 790 | for ($i = 1; $i < $monthIndex; ++$i) { |
| 791 | 791 | $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; |
| 792 | 792 | } |
| 793 | 793 | |
| 794 | - $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
|
| 794 | + $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int)$this->recur["monthday"]); |
|
| 795 | 795 | } |
| 796 | 796 | |
| 797 | 797 | break; |
@@ -803,11 +803,11 @@ discard block |
||
| 803 | 803 | return; |
| 804 | 804 | } |
| 805 | 805 | |
| 806 | - $weekdays = (int) $this->recur["weekdays"]; |
|
| 807 | - $nday = (int) $this->recur["nday"]; |
|
| 806 | + $weekdays = (int)$this->recur["weekdays"]; |
|
| 807 | + $nday = (int)$this->recur["nday"]; |
|
| 808 | 808 | |
| 809 | 809 | // Calc startdate |
| 810 | - $monthbegindow = (int) $this->recur["start"]; |
|
| 810 | + $monthbegindow = (int)$this->recur["start"]; |
|
| 811 | 811 | |
| 812 | 812 | if ($nday == 5) { |
| 813 | 813 | // Set date on the last day of the last month |
@@ -842,12 +842,12 @@ discard block |
||
| 842 | 842 | |
| 843 | 843 | $dayofweek = gmdate("w", $monthbegindow); |
| 844 | 844 | for ($i = 0; $i < 7; ++$i) { |
| 845 | - if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 845 | + if ($nday == 5 && (($dayofweek - $i) % 7 >= 0) && (1 << (($dayofweek - $i) % 7))&$weekdays) { |
|
| 846 | 846 | $day = gmdate("j", $monthbegindow) - $i; |
| 847 | 847 | |
| 848 | 848 | break; |
| 849 | 849 | } |
| 850 | - if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 850 | + if ($nday != 5 && (1 << (($dayofweek + $i) % 7))&$weekdays) { |
|
| 851 | 851 | $day = (($nday - 1) * 7) + ($i + 1); |
| 852 | 852 | |
| 853 | 853 | break; |
@@ -855,7 +855,7 @@ discard block |
||
| 855 | 855 | } |
| 856 | 856 | |
| 857 | 857 | // Goto the next X month |
| 858 | - if (isset($day) && ($day < gmdate("j", (int) $this->recur["start"]))) { |
|
| 858 | + if (isset($day) && ($day < gmdate("j", (int)$this->recur["start"]))) { |
|
| 859 | 859 | if ($nday == 5) { |
| 860 | 860 | $monthbegindow += 24 * 60 * 60; |
| 861 | 861 | if ($curmonth == 12) { |
@@ -887,12 +887,12 @@ discard block |
||
| 887 | 887 | // Set start on the right day |
| 888 | 888 | $dayofweek = gmdate("w", $monthbegindow); |
| 889 | 889 | for ($i = 0; $i < 7; ++$i) { |
| 890 | - if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 890 | + if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7))&$weekdays) { |
|
| 891 | 891 | $day = $i; |
| 892 | 892 | |
| 893 | 893 | break; |
| 894 | 894 | } |
| 895 | - if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 895 | + if ($nday != 5 && (1 << (($dayofweek + $i) % 7))&$weekdays) { |
|
| 896 | 896 | $day = ($nday - 1) * 7 + ($i + 1); |
| 897 | 897 | |
| 898 | 898 | break; |
@@ -909,7 +909,7 @@ discard block |
||
| 909 | 909 | |
| 910 | 910 | if ($term == 0x0C /* monthly */) { |
| 911 | 911 | // Calc first occ |
| 912 | - $monthIndex = ((((12 % $everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 912 | + $monthIndex = ((((12 % $everyn) * (((int)gmdate("Y", $this->recur["start"]) - 1601) % $everyn)) % $everyn) + (((int)gmdate("n", $this->recur["start"])) - 1)) % $everyn; |
|
| 913 | 913 | |
| 914 | 914 | for ($i = 0; $i < $monthIndex; ++$i) { |
| 915 | 915 | $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), ($i % 12) + 1) / 60; |
@@ -919,7 +919,7 @@ discard block |
||
| 919 | 919 | } |
| 920 | 920 | else { |
| 921 | 921 | // Calc first occ |
| 922 | - $monthIndex = (int) gmdate("n", $this->recur["start"]); |
|
| 922 | + $monthIndex = (int)gmdate("n", $this->recur["start"]); |
|
| 923 | 923 | |
| 924 | 924 | for ($i = 1; $i < $monthIndex; ++$i) { |
| 925 | 925 | $firstocc += $this->getMonthInSeconds(1601 + floor($i / 12), $i) / 60; |
@@ -939,7 +939,7 @@ discard block |
||
| 939 | 939 | } |
| 940 | 940 | |
| 941 | 941 | // Terminate |
| 942 | - $term = (int) $this->recur["term"]; |
|
| 942 | + $term = (int)$this->recur["term"]; |
|
| 943 | 943 | $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00); |
| 944 | 944 | |
| 945 | 945 | switch ($term) { |
@@ -954,7 +954,7 @@ discard block |
||
| 954 | 954 | return; |
| 955 | 955 | } |
| 956 | 956 | |
| 957 | - $rdata .= pack("V", (int) $this->recur["numoccur"]); |
|
| 957 | + $rdata .= pack("V", (int)$this->recur["numoccur"]); |
|
| 958 | 958 | |
| 959 | 959 | break; |
| 960 | 960 | // Never ends |
@@ -965,7 +965,7 @@ discard block |
||
| 965 | 965 | } |
| 966 | 966 | |
| 967 | 967 | // Strange little thing for the recurrence type "every workday" |
| 968 | - if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) { |
|
| 968 | + if (((int)$this->recur["type"]) == 0x0B && ((int)$this->recur["subtype"]) == 1) { |
|
| 969 | 969 | $rdata .= pack("V", 1); |
| 970 | 970 | } |
| 971 | 971 | else { // Other recurrences |
@@ -1009,25 +1009,25 @@ discard block |
||
| 1009 | 1009 | } |
| 1010 | 1010 | |
| 1011 | 1011 | // Set start date |
| 1012 | - $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"])); |
|
| 1012 | + $rdata .= pack("V", $this->unixDataToRecurData((int)$this->recur["start"])); |
|
| 1013 | 1013 | |
| 1014 | 1014 | // Set enddate |
| 1015 | 1015 | switch ($term) { |
| 1016 | 1016 | // After the given enddate |
| 1017 | 1017 | case 0x21: |
| 1018 | - $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
|
| 1018 | + $rdata .= pack("V", $this->unixDataToRecurData((int)$this->recur["end"])); |
|
| 1019 | 1019 | |
| 1020 | 1020 | break; |
| 1021 | 1021 | // After a number of times |
| 1022 | 1022 | case 0x22: |
| 1023 | 1023 | // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour |
| 1024 | - $occenddate = (int) $this->recur["start"]; |
|
| 1024 | + $occenddate = (int)$this->recur["start"]; |
|
| 1025 | 1025 | |
| 1026 | - switch ((int) $this->recur["type"]) { |
|
| 1026 | + switch ((int)$this->recur["type"]) { |
|
| 1027 | 1027 | case 0x0A: // daily |
| 1028 | 1028 | if ($this->recur["subtype"] == 1) { |
| 1029 | 1029 | // Daily every workday |
| 1030 | - $restocc = (int) $this->recur["numoccur"]; |
|
| 1030 | + $restocc = (int)$this->recur["numoccur"]; |
|
| 1031 | 1031 | |
| 1032 | 1032 | // Get starting weekday |
| 1033 | 1033 | $nowtime = $this->gmtime($occenddate); |
@@ -1049,7 +1049,7 @@ discard block |
||
| 1049 | 1049 | } |
| 1050 | 1050 | else { |
| 1051 | 1051 | // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence) |
| 1052 | - $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1))); |
|
| 1052 | + $occenddate += (((int)$this->recur["everyn"]) * 60 * (((int)$this->recur["numoccur"] - 1))); |
|
| 1053 | 1053 | } |
| 1054 | 1054 | |
| 1055 | 1055 | break; |
@@ -1068,11 +1068,11 @@ discard block |
||
| 1068 | 1068 | for ($j = 1; $restocc > 0; ++$j) { |
| 1069 | 1069 | // Jump to the next week (which may be N weeks away) when going over the week boundary |
| 1070 | 1070 | if ((($dayofweek + $j) % 7) == $weekstart) { |
| 1071 | - $occenddate += (((int) $this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60; |
|
| 1071 | + $occenddate += (((int)$this->recur["everyn"]) - 1) * 7 * 24 * 60 * 60; |
|
| 1072 | 1072 | } |
| 1073 | 1073 | |
| 1074 | 1074 | // If this is a matching day, once less occurrence to process |
| 1075 | - if (((int) $this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 1075 | + if (((int)$this->recur["weekdays"]) & (1 << (($dayofweek + $j) % 7))) { |
|
| 1076 | 1076 | --$restocc; |
| 1077 | 1077 | } |
| 1078 | 1078 | |
@@ -1084,11 +1084,11 @@ discard block |
||
| 1084 | 1084 | |
| 1085 | 1085 | case 0x0C: // monthly |
| 1086 | 1086 | case 0x0D: // yearly |
| 1087 | - $curyear = gmdate("Y", (int) $this->recur["start"]); |
|
| 1088 | - $curmonth = gmdate("n", (int) $this->recur["start"]); |
|
| 1087 | + $curyear = gmdate("Y", (int)$this->recur["start"]); |
|
| 1088 | + $curmonth = gmdate("n", (int)$this->recur["start"]); |
|
| 1089 | 1089 | // $forwardcount = months |
| 1090 | 1090 | |
| 1091 | - switch ((int) $this->recur["subtype"]) { |
|
| 1091 | + switch ((int)$this->recur["subtype"]) { |
|
| 1092 | 1092 | case 2: // on D day of every M month |
| 1093 | 1093 | while ($forwardcount > 0) { |
| 1094 | 1094 | $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
@@ -1104,8 +1104,8 @@ discard block |
||
| 1104 | 1104 | } |
| 1105 | 1105 | |
| 1106 | 1106 | // compensation between 28 and 31 |
| 1107 | - if (((int) $this->recur["monthday"]) >= 28 && ((int) $this->recur["monthday"]) <= 31 && |
|
| 1108 | - gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) { |
|
| 1107 | + if (((int)$this->recur["monthday"]) >= 28 && ((int)$this->recur["monthday"]) <= 31 && |
|
| 1108 | + gmdate("j", $occenddate) < ((int)$this->recur["monthday"])) { |
|
| 1109 | 1109 | if (gmdate("j", $occenddate) < 28) { |
| 1110 | 1110 | $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60; |
| 1111 | 1111 | } |
@@ -1117,8 +1117,8 @@ discard block |
||
| 1117 | 1117 | break; |
| 1118 | 1118 | |
| 1119 | 1119 | case 3: // on Nth weekday of every M month |
| 1120 | - $nday = (int) $this->recur["nday"]; // 1 tot 5 |
|
| 1121 | - $weekdays = (int) $this->recur["weekdays"]; |
|
| 1120 | + $nday = (int)$this->recur["nday"]; // 1 tot 5 |
|
| 1121 | + $weekdays = (int)$this->recur["weekdays"]; |
|
| 1122 | 1122 | |
| 1123 | 1123 | while ($forwardcount > 0) { |
| 1124 | 1124 | $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
@@ -1144,12 +1144,12 @@ discard block |
||
| 1144 | 1144 | |
| 1145 | 1145 | $dayofweek = gmdate("w", $occenddate); |
| 1146 | 1146 | for ($i = 0; $i < 7; ++$i) { |
| 1147 | - if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7)) & $weekdays) { |
|
| 1147 | + if ($nday == 5 && (($dayofweek - $i) % 7) >= 0 && (1 << (($dayofweek - $i) % 7))&$weekdays) { |
|
| 1148 | 1148 | $occenddate -= $i * 24 * 60 * 60; |
| 1149 | 1149 | |
| 1150 | 1150 | break; |
| 1151 | 1151 | } |
| 1152 | - if ($nday != 5 && (1 << (($dayofweek + $i) % 7)) & $weekdays) { |
|
| 1152 | + if ($nday != 5 && (1 << (($dayofweek + $i) % 7))&$weekdays) { |
|
| 1153 | 1153 | $occenddate += ($i + (($nday - 1) * 7)) * 24 * 60 * 60; |
| 1154 | 1154 | |
| 1155 | 1155 | break; |
@@ -1168,7 +1168,7 @@ discard block |
||
| 1168 | 1168 | |
| 1169 | 1169 | $this->recur["end"] = $occenddate; |
| 1170 | 1170 | |
| 1171 | - $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
|
| 1171 | + $rdata .= pack("V", $this->unixDataToRecurData((int)$this->recur["end"])); |
|
| 1172 | 1172 | |
| 1173 | 1173 | break; |
| 1174 | 1174 | // Never ends |
@@ -1181,12 +1181,12 @@ discard block |
||
| 1181 | 1181 | } |
| 1182 | 1182 | |
| 1183 | 1183 | // UTC date |
| 1184 | - $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]); |
|
| 1185 | - $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]); |
|
| 1184 | + $utcstart = $this->toGMT($this->tz, (int)$this->recur["start"]); |
|
| 1185 | + $utcend = $this->toGMT($this->tz, (int)$this->recur["end"]); |
|
| 1186 | 1186 | |
| 1187 | 1187 | // utc date+time |
| 1188 | - $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"]) * 60) : $utcstart; |
|
| 1189 | - $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart; |
|
| 1188 | + $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int)$this->recur["startocc"]) * 60) : $utcstart; |
|
| 1189 | + $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int)$this->recur["endocc"]) * 60) : $utcstart; |
|
| 1190 | 1190 | |
| 1191 | 1191 | $propsToSet = []; |
| 1192 | 1192 | // update reminder time |
@@ -1204,7 +1204,7 @@ discard block |
||
| 1204 | 1204 | |
| 1205 | 1205 | // recurrencetype |
| 1206 | 1206 | // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype |
| 1207 | - $propsToSet[$this->proptags["recurrencetype"]] = ((int) $this->recur["type"]) - 0x9; |
|
| 1207 | + $propsToSet[$this->proptags["recurrencetype"]] = ((int)$this->recur["type"]) - 0x9; |
|
| 1208 | 1208 | |
| 1209 | 1209 | // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting |
| 1210 | 1210 | $propsToSet[$this->proptags["side_effects"]] = 369; |
@@ -1253,7 +1253,7 @@ discard block |
||
| 1253 | 1253 | |
| 1254 | 1254 | if (isset($this->recur["startocc"], $this->recur["endocc"])) { |
| 1255 | 1255 | // Set start and endtime in minutes |
| 1256 | - $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]); |
|
| 1256 | + $rdata .= pack("VV", (int)$this->recur["startocc"], (int)$this->recur["endocc"]); |
|
| 1257 | 1257 | } |
| 1258 | 1258 | |
| 1259 | 1259 | // Detailed exception data |
@@ -1308,7 +1308,7 @@ discard block |
||
| 1308 | 1308 | $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]); |
| 1309 | 1309 | $length = strlen($subject); |
| 1310 | 1310 | $rdata .= pack("vv", $length + 1, $length); |
| 1311 | - $rdata .= pack("a" . $length, $subject); |
|
| 1311 | + $rdata .= pack("a".$length, $subject); |
|
| 1312 | 1312 | } |
| 1313 | 1313 | |
| 1314 | 1314 | if (isset($changed_item["remind_before"])) { |
@@ -1323,7 +1323,7 @@ discard block |
||
| 1323 | 1323 | $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]); |
| 1324 | 1324 | $length = strlen($location); |
| 1325 | 1325 | $rdata .= pack("vv", $length + 1, $length); |
| 1326 | - $rdata .= pack("a" . $length, $location); |
|
| 1326 | + $rdata .= pack("a".$length, $location); |
|
| 1327 | 1327 | } |
| 1328 | 1328 | |
| 1329 | 1329 | if (isset($changed_item["busystatus"])) { |
@@ -1354,14 +1354,14 @@ discard block |
||
| 1354 | 1354 | $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]); |
| 1355 | 1355 | $length = iconv_strlen($subject, "UCS-2LE"); |
| 1356 | 1356 | $rdata .= pack("v", $length); |
| 1357 | - $rdata .= pack("a" . $length * 2, $subject); |
|
| 1357 | + $rdata .= pack("a".$length * 2, $subject); |
|
| 1358 | 1358 | } |
| 1359 | 1359 | |
| 1360 | 1360 | if (isset($changed_item["location"])) { |
| 1361 | 1361 | $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]); |
| 1362 | 1362 | $length = iconv_strlen($location, "UCS-2LE"); |
| 1363 | 1363 | $rdata .= pack("v", $length); |
| 1364 | - $rdata .= pack("a" . $length * 2, $location); |
|
| 1364 | + $rdata .= pack("a".$length * 2, $location); |
|
| 1365 | 1365 | } |
| 1366 | 1366 | |
| 1367 | 1367 | if (isset($changed_item["subject"]) || isset($changed_item["location"])) { |
@@ -1918,7 +1918,7 @@ discard block |
||
| 1918 | 1918 | } |
| 1919 | 1919 | elseif ($this->recur['regen']) { |
| 1920 | 1920 | $year_starttime = $this->gmtime($now); |
| 1921 | - $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
|
| 1921 | + $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
|
| 1922 | 1922 | $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */); |
| 1923 | 1923 | |
| 1924 | 1924 | if ($now <= $dayend) { |
@@ -53,8 +53,7 @@ discard block |
||
| 53 | 53 | |
| 54 | 54 | if (is_array($message)) { |
| 55 | 55 | $this->messageprops = $message; |
| 56 | - } |
|
| 57 | - else { |
|
| 56 | + } else { |
|
| 58 | 57 | $this->message = $message; |
| 59 | 58 | $this->messageprops = mapi_getprops($this->message, $this->proptags); |
| 60 | 59 | } |
@@ -218,8 +217,7 @@ discard block |
||
| 218 | 217 | |
| 219 | 218 | if ($ret["subtype"] == 3) { |
| 220 | 219 | $ret["weekdays"] = $data["monthday"]; |
| 221 | - } |
|
| 222 | - else { |
|
| 220 | + } else { |
|
| 223 | 221 | $ret["monthday"] = $data["monthday"]; |
| 224 | 222 | } |
| 225 | 223 | |
@@ -247,8 +245,7 @@ discard block |
||
| 247 | 245 | |
| 248 | 246 | if ($ret["subtype"] == 3) { |
| 249 | 247 | $ret["weekdays"] = $data["monthday"]; |
| 250 | - } |
|
| 251 | - else { |
|
| 248 | + } else { |
|
| 252 | 249 | $ret["monthday"] = $data["monthday"]; |
| 253 | 250 | } |
| 254 | 251 | |
@@ -592,8 +589,7 @@ discard block |
||
| 592 | 589 | if ($this->recur["subtype"] == 1) { |
| 593 | 590 | // Daily every workday |
| 594 | 591 | $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E); |
| 595 | - } |
|
| 596 | - else { |
|
| 592 | + } else { |
|
| 597 | 593 | // Daily every N days (everyN in minutes) |
| 598 | 594 | |
| 599 | 595 | $everyn = ((int) $this->recur["everyn"]) / 1440; |
@@ -669,8 +665,7 @@ discard block |
||
| 669 | 665 | |
| 670 | 666 | if ($this->recur["regen"]) { |
| 671 | 667 | $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1); |
| 672 | - } |
|
| 673 | - else { |
|
| 668 | + } else { |
|
| 674 | 669 | $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]); |
| 675 | 670 | } |
| 676 | 671 | |
@@ -689,8 +684,7 @@ discard block |
||
| 689 | 684 | |
| 690 | 685 | if ($term == 0x0C /* monthly */) { |
| 691 | 686 | $everyn = (int) $this->recur["everyn"]; |
| 692 | - } |
|
| 693 | - else { |
|
| 687 | + } else { |
|
| 694 | 688 | $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12; |
| 695 | 689 | } |
| 696 | 690 | |
@@ -733,18 +727,15 @@ discard block |
||
| 733 | 727 | if ($term == 0x0D /* yearly */) { |
| 734 | 728 | if ($curmonth > $selmonth) {// go to next occurrence in 'everyn' months minus difference in first occurrence and original date |
| 735 | 729 | $count = $everyn - ($curmonth - $selmonth); |
| 736 | - } |
|
| 737 | - elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date |
|
| 730 | + } elseif ($curmonth < $selmonth) {// go to next occurrence upto difference in first occurrence and original date |
|
| 738 | 731 | $count = $selmonth - $curmonth; |
| 739 | - } |
|
| 740 | - else { |
|
| 732 | + } else { |
|
| 741 | 733 | // Go to next occurrence while recurrence start date is greater than occurrence date but within same month |
| 742 | 734 | if (((int) $this->recur["monthday"]) < $curmonthday) { |
| 743 | 735 | $count = $everyn; |
| 744 | 736 | } |
| 745 | 737 | } |
| 746 | - } |
|
| 747 | - else { |
|
| 738 | + } else { |
|
| 748 | 739 | $count = $everyn; // Monthly, go to next occurrence in 'everyn' months |
| 749 | 740 | } |
| 750 | 741 | |
@@ -782,8 +773,7 @@ discard block |
||
| 782 | 773 | } |
| 783 | 774 | |
| 784 | 775 | $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
| 785 | - } |
|
| 786 | - else { |
|
| 776 | + } else { |
|
| 787 | 777 | // Calc first occ |
| 788 | 778 | $firstocc = 0; |
| 789 | 779 | $monthIndex = (int) gmdate("n", $this->recur["start"]); |
@@ -812,8 +802,7 @@ discard block |
||
| 812 | 802 | if ($nday == 5) { |
| 813 | 803 | // Set date on the last day of the last month |
| 814 | 804 | $monthbegindow += (gmdate("t", $monthbegindow) - gmdate("j", $monthbegindow)) * 24 * 60 * 60; |
| 815 | - } |
|
| 816 | - else { |
|
| 805 | + } else { |
|
| 817 | 806 | // Set on the first day of the month |
| 818 | 807 | $monthbegindow -= ((gmdate("j", $monthbegindow) - 1) * 24 * 60 * 60); |
| 819 | 808 | } |
@@ -822,8 +811,7 @@ discard block |
||
| 822 | 811 | // Set on right month |
| 823 | 812 | if ($selmonth < $curmonth) { |
| 824 | 813 | $tmp = 12 - $curmonth + $selmonth; |
| 825 | - } |
|
| 826 | - else { |
|
| 814 | + } else { |
|
| 827 | 815 | $tmp = ($selmonth - $curmonth); |
| 828 | 816 | } |
| 829 | 817 | |
@@ -836,8 +824,7 @@ discard block |
||
| 836 | 824 | } |
| 837 | 825 | ++$curmonth; |
| 838 | 826 | } |
| 839 | - } |
|
| 840 | - else { |
|
| 827 | + } else { |
|
| 841 | 828 | // Check or you exist in the right month |
| 842 | 829 | |
| 843 | 830 | $dayofweek = gmdate("w", $monthbegindow); |
@@ -900,8 +887,7 @@ discard block |
||
| 900 | 887 | } |
| 901 | 888 | if ($nday == 5) { |
| 902 | 889 | $monthbegindow -= $day * 24 * 60 * 60; |
| 903 | - } |
|
| 904 | - else { |
|
| 890 | + } else { |
|
| 905 | 891 | $monthbegindow += ($day - 1) * 24 * 60 * 60; |
| 906 | 892 | } |
| 907 | 893 | |
@@ -916,8 +902,7 @@ discard block |
||
| 916 | 902 | } |
| 917 | 903 | |
| 918 | 904 | $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
| 919 | - } |
|
| 920 | - else { |
|
| 905 | + } else { |
|
| 921 | 906 | // Calc first occ |
| 922 | 907 | $monthIndex = (int) gmdate("n", $this->recur["start"]); |
| 923 | 908 | |
@@ -967,8 +952,7 @@ discard block |
||
| 967 | 952 | // Strange little thing for the recurrence type "every workday" |
| 968 | 953 | if (((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) { |
| 969 | 954 | $rdata .= pack("V", 1); |
| 970 | - } |
|
| 971 | - else { // Other recurrences |
|
| 955 | + } else { // Other recurrences |
|
| 972 | 956 | $rdata .= pack("V", 0); |
| 973 | 957 | } |
| 974 | 958 | |
@@ -1046,8 +1030,7 @@ discard block |
||
| 1046 | 1030 | |
| 1047 | 1031 | $occenddate += 24 * 60 * 60; |
| 1048 | 1032 | } |
| 1049 | - } |
|
| 1050 | - else { |
|
| 1033 | + } else { |
|
| 1051 | 1034 | // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence) |
| 1052 | 1035 | $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"] - 1))); |
| 1053 | 1036 | } |
@@ -1096,8 +1079,7 @@ discard block |
||
| 1096 | 1079 | if ($curmonth >= 12) { |
| 1097 | 1080 | $curmonth = 1; |
| 1098 | 1081 | ++$curyear; |
| 1099 | - } |
|
| 1100 | - else { |
|
| 1082 | + } else { |
|
| 1101 | 1083 | ++$curmonth; |
| 1102 | 1084 | } |
| 1103 | 1085 | --$forwardcount; |
@@ -1108,8 +1090,7 @@ discard block |
||
| 1108 | 1090 | gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) { |
| 1109 | 1091 | if (gmdate("j", $occenddate) < 28) { |
| 1110 | 1092 | $occenddate -= gmdate("j", $occenddate) * 24 * 60 * 60; |
| 1111 | - } |
|
| 1112 | - else { |
|
| 1093 | + } else { |
|
| 1113 | 1094 | $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; |
| 1114 | 1095 | } |
| 1115 | 1096 | } |
@@ -1125,8 +1106,7 @@ discard block |
||
| 1125 | 1106 | if ($curmonth >= 12) { |
| 1126 | 1107 | $curmonth = 1; |
| 1127 | 1108 | ++$curyear; |
| 1128 | - } |
|
| 1129 | - else { |
|
| 1109 | + } else { |
|
| 1130 | 1110 | ++$curmonth; |
| 1131 | 1111 | } |
| 1132 | 1112 | |
@@ -1136,8 +1116,7 @@ discard block |
||
| 1136 | 1116 | if ($nday == 5) { |
| 1137 | 1117 | // Set date on the last day of the last month |
| 1138 | 1118 | $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 * 60; |
| 1139 | - } |
|
| 1140 | - else { |
|
| 1119 | + } else { |
|
| 1141 | 1120 | // Set date on the first day of the last month |
| 1142 | 1121 | $occenddate -= (gmdate("j", $occenddate) - 1) * 24 * 60 * 60; |
| 1143 | 1122 | } |
@@ -1208,8 +1187,7 @@ discard block |
||
| 1208 | 1187 | |
| 1209 | 1188 | // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting |
| 1210 | 1189 | $propsToSet[$this->proptags["side_effects"]] = 369; |
| 1211 | - } |
|
| 1212 | - else { |
|
| 1190 | + } else { |
|
| 1213 | 1191 | $propsToSet[$this->proptags["side_effects"]] = 3441; |
| 1214 | 1192 | } |
| 1215 | 1193 | |
@@ -1239,8 +1217,7 @@ discard block |
||
| 1239 | 1217 | |
| 1240 | 1218 | if ($occ) { |
| 1241 | 1219 | $propsToSet[$this->proptags["flagdueby"]] = $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60); |
| 1242 | - } |
|
| 1243 | - else { |
|
| 1220 | + } else { |
|
| 1244 | 1221 | // Last reminder passed, no reminders any more. |
| 1245 | 1222 | $propsToSet[$this->proptags["reminder"]] = false; |
| 1246 | 1223 | $propsToSet[$this->proptags["flagdueby"]] = 0x7FF00000; |
@@ -1459,11 +1436,9 @@ discard block |
||
| 1459 | 1436 | public function getMonthInSeconds($year, $month) { |
| 1460 | 1437 | if (in_array($month, [1, 3, 5, 7, 8, 10, 12])) { |
| 1461 | 1438 | $day = 31; |
| 1462 | - } |
|
| 1463 | - elseif (in_array($month, [4, 6, 9, 11])) { |
|
| 1439 | + } elseif (in_array($month, [4, 6, 9, 11])) { |
|
| 1464 | 1440 | $day = 30; |
| 1465 | - } |
|
| 1466 | - else { |
|
| 1441 | + } else { |
|
| 1467 | 1442 | $day = 28; |
| 1468 | 1443 | if ($this->isLeapYear($year) == 1) { |
| 1469 | 1444 | ++$day; |
@@ -1533,8 +1508,7 @@ discard block |
||
| 1533 | 1508 | if ($date > $dststart && $date < $dstend) { |
| 1534 | 1509 | $dst = true; |
| 1535 | 1510 | } |
| 1536 | - } |
|
| 1537 | - else { |
|
| 1511 | + } else { |
|
| 1538 | 1512 | // Southern hemisphere, eg DST is during Oct-Mar |
| 1539 | 1513 | if ($date < $dstend || $date > $dststart) { |
| 1540 | 1514 | $dst = true; |
@@ -1720,8 +1694,7 @@ discard block |
||
| 1720 | 1694 | // TODO use one isset |
| 1721 | 1695 | if (isset($this->recur['regen'], $this->action['datecompleted']) && $this->recur['regen']) { |
| 1722 | 1696 | $daystart = $this->dayStartOf($this->action['datecompleted']); |
| 1723 | - } |
|
| 1724 | - else { |
|
| 1697 | + } else { |
|
| 1725 | 1698 | $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence |
| 1726 | 1699 | } |
| 1727 | 1700 | |
@@ -1729,8 +1702,7 @@ discard block |
||
| 1729 | 1702 | // or the end of the recurrence, whichever comes first |
| 1730 | 1703 | if ($end > $this->toGMT($this->tz, $this->recur["end"])) { |
| 1731 | 1704 | $rangeend = $this->toGMT($this->tz, $this->recur["end"]); |
| 1732 | - } |
|
| 1733 | - else { |
|
| 1705 | + } else { |
|
| 1734 | 1706 | $rangeend = $end; |
| 1735 | 1707 | } |
| 1736 | 1708 | |
@@ -1750,8 +1722,7 @@ discard block |
||
| 1750 | 1722 | for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) { |
| 1751 | 1723 | $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
| 1752 | 1724 | } |
| 1753 | - } |
|
| 1754 | - else { |
|
| 1725 | + } else { |
|
| 1755 | 1726 | // Every workday |
| 1756 | 1727 | for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) { |
| 1757 | 1728 | $nowtime = $this->gmtime($now); |
@@ -1777,8 +1748,7 @@ discard block |
||
| 1777 | 1748 | for ($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) { |
| 1778 | 1749 | if ($this->recur['regen']) { |
| 1779 | 1750 | $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
| 1780 | - } |
|
| 1781 | - else { |
|
| 1751 | + } else { |
|
| 1782 | 1752 | // Loop through the whole following week to the first occurrence of the week, add each day that is specified |
| 1783 | 1753 | for ($wday = 0; $wday < 7; ++$wday) { |
| 1784 | 1754 | $daynow = $now + $wday * 60 * 60 * 24; |
@@ -1813,8 +1783,7 @@ discard block |
||
| 1813 | 1783 | if ($daynow <= $dayend) { |
| 1814 | 1784 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
| 1815 | 1785 | } |
| 1816 | - } |
|
| 1817 | - elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months |
|
| 1786 | + } elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] of every N months |
|
| 1818 | 1787 | // Sanitize input |
| 1819 | 1788 | if ($this->recur["weekdays"] == 0) { |
| 1820 | 1789 | $this->recur["weekdays"] = 1; |
@@ -1841,8 +1810,7 @@ discard block |
||
| 1841 | 1810 | } |
| 1842 | 1811 | // $firstday is the day of the month on which the asked pattern of nth weekday matches |
| 1843 | 1812 | $daynow = $now + $firstday * 60 * 60 * 24; |
| 1844 | - } |
|
| 1845 | - else { |
|
| 1813 | + } else { |
|
| 1846 | 1814 | // Find last day in the month ($now is the firstday of the month) |
| 1847 | 1815 | $NumDaysInMonth = $this->daysInMonth($now, 1); |
| 1848 | 1816 | $daynow = $now + (($NumDaysInMonth - 1) * 24 * 60 * 60); |
@@ -1860,8 +1828,7 @@ discard block |
||
| 1860 | 1828 | if ($daynow <= $dayend && $daynow >= $daystart) { |
| 1861 | 1829 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
| 1862 | 1830 | } |
| 1863 | - } |
|
| 1864 | - elseif ($this->recur['regen']) { |
|
| 1831 | + } elseif ($this->recur['regen']) { |
|
| 1865 | 1832 | $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60); |
| 1866 | 1833 | $now = $daystart + ($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60); |
| 1867 | 1834 | |
@@ -1890,8 +1857,7 @@ discard block |
||
| 1890 | 1857 | } // Cap $monthday on month length (eg 28 feb instead of 29 feb) |
| 1891 | 1858 | $daynow = $monthstart + ($monthday - 1) * 24 * 60 * 60; |
| 1892 | 1859 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
| 1893 | - } |
|
| 1894 | - elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
|
| 1860 | + } elseif (isset($this->recur["nday"], $this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
|
| 1895 | 1861 | // Go the correct month |
| 1896 | 1862 | $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60; |
| 1897 | 1863 | |
@@ -1915,8 +1881,7 @@ discard block |
||
| 1915 | 1881 | } |
| 1916 | 1882 | |
| 1917 | 1883 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
| 1918 | - } |
|
| 1919 | - elseif ($this->recur['regen']) { |
|
| 1884 | + } elseif ($this->recur['regen']) { |
|
| 1920 | 1885 | $year_starttime = $this->gmtime($now); |
| 1921 | 1886 | $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
| 1922 | 1887 | $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /* year in seconds */); |
@@ -17,129 +17,129 @@ |
||
| 17 | 17 | * getTraceAsString() - formated string of trace |
| 18 | 18 | */ |
| 19 | 19 | class BaseException extends Exception { |
| 20 | - /** |
|
| 21 | - * Base name of the file, so we don't have to use static path of the file. |
|
| 22 | - */ |
|
| 23 | - private $baseFile; |
|
| 24 | - |
|
| 25 | - /** |
|
| 26 | - * Flag to check if exception is already handled or not. |
|
| 27 | - */ |
|
| 28 | - public $isHandled = false; |
|
| 29 | - |
|
| 30 | - /** |
|
| 31 | - * The exception message to show at client side. |
|
| 32 | - */ |
|
| 33 | - public $displayMessage; |
|
| 34 | - |
|
| 35 | - /** |
|
| 36 | - * Flag for allow to exception details message or not. |
|
| 37 | - */ |
|
| 38 | - public $allowToShowDetailsMessage = false; |
|
| 39 | - |
|
| 40 | - /** |
|
| 41 | - * The exception title to show as a message box title at client side. |
|
| 42 | - */ |
|
| 43 | - public $title; |
|
| 44 | - |
|
| 45 | - /** |
|
| 46 | - * Construct the exception. |
|
| 47 | - * |
|
| 48 | - * @param string $errorMessage |
|
| 49 | - * @param int $code |
|
| 50 | - * @param Exception $previous |
|
| 51 | - * @param string $displayMessage |
|
| 52 | - */ |
|
| 53 | - public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) { |
|
| 54 | - // assign display message |
|
| 55 | - $this->displayMessage = $displayMessage; |
|
| 56 | - |
|
| 57 | - parent::__construct($errorMessage, (int) $code, $previous); |
|
| 58 | - } |
|
| 59 | - |
|
| 60 | - /** |
|
| 61 | - * @return string returns file name and line number combined where exception occurred |
|
| 62 | - */ |
|
| 63 | - public function getFileLine() { |
|
| 64 | - return $this->getBaseFile() . ':' . $this->getLine(); |
|
| 65 | - } |
|
| 66 | - |
|
| 67 | - /** |
|
| 68 | - * @return string returns message that should be sent to client to display |
|
| 69 | - */ |
|
| 70 | - public function getDisplayMessage() { |
|
| 71 | - if (!is_null($this->displayMessage)) { |
|
| 72 | - return $this->displayMessage; |
|
| 73 | - } |
|
| 74 | - |
|
| 75 | - return $this->getMessage(); |
|
| 76 | - } |
|
| 77 | - |
|
| 78 | - /** |
|
| 79 | - * Function sets display message of an exception that will be sent to the client side |
|
| 80 | - * to show it to user. |
|
| 81 | - * |
|
| 82 | - * @param string $message display message |
|
| 83 | - */ |
|
| 84 | - public function setDisplayMessage($message) { |
|
| 85 | - $this->displayMessage = $message; |
|
| 86 | - } |
|
| 87 | - |
|
| 88 | - /** |
|
| 89 | - * Function sets title of an exception that will be sent to the client side |
|
| 90 | - * to show it to user. |
|
| 91 | - * |
|
| 92 | - * @param string $title title of an exception |
|
| 93 | - */ |
|
| 94 | - public function setTitle($title) { |
|
| 95 | - $this->title = $title; |
|
| 96 | - } |
|
| 97 | - |
|
| 98 | - /** |
|
| 99 | - * @return string returns title that should be sent to client to display as a message box |
|
| 100 | - * title |
|
| 101 | - */ |
|
| 102 | - public function getTitle() { |
|
| 103 | - return $this->title; |
|
| 104 | - } |
|
| 105 | - |
|
| 106 | - /** |
|
| 107 | - * Function sets a flag in exception class to indicate that exception is already handled |
|
| 108 | - * so if it is caught again in the top level of function stack then we have to silently |
|
| 109 | - * ignore it. |
|
| 110 | - */ |
|
| 111 | - public function setHandled() { |
|
| 112 | - $this->isHandled = true; |
|
| 113 | - } |
|
| 114 | - |
|
| 115 | - /** |
|
| 116 | - * @return string returns base path of the file where exception occurred |
|
| 117 | - */ |
|
| 118 | - public function getBaseFile() { |
|
| 119 | - if (is_null($this->baseFile)) { |
|
| 120 | - $this->baseFile = basename(parent::getFile()); |
|
| 121 | - } |
|
| 122 | - |
|
| 123 | - return $this->baseFile; |
|
| 124 | - } |
|
| 125 | - |
|
| 126 | - /** |
|
| 127 | - * Name of the class of exception. |
|
| 128 | - * |
|
| 129 | - * @return string |
|
| 130 | - */ |
|
| 131 | - public function getName() { |
|
| 132 | - return get_class($this); |
|
| 133 | - } |
|
| 134 | - |
|
| 135 | - /** |
|
| 136 | - * It will return details error message if allowToShowDetailsMessage is set. |
|
| 137 | - * |
|
| 138 | - * @return string returns details error message |
|
| 139 | - */ |
|
| 140 | - public function getDetailsMessage() { |
|
| 141 | - return $this->allowToShowDetailsMessage ? $this->__toString() : ''; |
|
| 142 | - } |
|
| 143 | - |
|
| 144 | - // @TODO getTrace and getTraceAsString |
|
| 20 | + /** |
|
| 21 | + * Base name of the file, so we don't have to use static path of the file. |
|
| 22 | + */ |
|
| 23 | + private $baseFile; |
|
| 24 | + |
|
| 25 | + /** |
|
| 26 | + * Flag to check if exception is already handled or not. |
|
| 27 | + */ |
|
| 28 | + public $isHandled = false; |
|
| 29 | + |
|
| 30 | + /** |
|
| 31 | + * The exception message to show at client side. |
|
| 32 | + */ |
|
| 33 | + public $displayMessage; |
|
| 34 | + |
|
| 35 | + /** |
|
| 36 | + * Flag for allow to exception details message or not. |
|
| 37 | + */ |
|
| 38 | + public $allowToShowDetailsMessage = false; |
|
| 39 | + |
|
| 40 | + /** |
|
| 41 | + * The exception title to show as a message box title at client side. |
|
| 42 | + */ |
|
| 43 | + public $title; |
|
| 44 | + |
|
| 45 | + /** |
|
| 46 | + * Construct the exception. |
|
| 47 | + * |
|
| 48 | + * @param string $errorMessage |
|
| 49 | + * @param int $code |
|
| 50 | + * @param Exception $previous |
|
| 51 | + * @param string $displayMessage |
|
| 52 | + */ |
|
| 53 | + public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) { |
|
| 54 | + // assign display message |
|
| 55 | + $this->displayMessage = $displayMessage; |
|
| 56 | + |
|
| 57 | + parent::__construct($errorMessage, (int) $code, $previous); |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + /** |
|
| 61 | + * @return string returns file name and line number combined where exception occurred |
|
| 62 | + */ |
|
| 63 | + public function getFileLine() { |
|
| 64 | + return $this->getBaseFile() . ':' . $this->getLine(); |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + /** |
|
| 68 | + * @return string returns message that should be sent to client to display |
|
| 69 | + */ |
|
| 70 | + public function getDisplayMessage() { |
|
| 71 | + if (!is_null($this->displayMessage)) { |
|
| 72 | + return $this->displayMessage; |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + return $this->getMessage(); |
|
| 76 | + } |
|
| 77 | + |
|
| 78 | + /** |
|
| 79 | + * Function sets display message of an exception that will be sent to the client side |
|
| 80 | + * to show it to user. |
|
| 81 | + * |
|
| 82 | + * @param string $message display message |
|
| 83 | + */ |
|
| 84 | + public function setDisplayMessage($message) { |
|
| 85 | + $this->displayMessage = $message; |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + /** |
|
| 89 | + * Function sets title of an exception that will be sent to the client side |
|
| 90 | + * to show it to user. |
|
| 91 | + * |
|
| 92 | + * @param string $title title of an exception |
|
| 93 | + */ |
|
| 94 | + public function setTitle($title) { |
|
| 95 | + $this->title = $title; |
|
| 96 | + } |
|
| 97 | + |
|
| 98 | + /** |
|
| 99 | + * @return string returns title that should be sent to client to display as a message box |
|
| 100 | + * title |
|
| 101 | + */ |
|
| 102 | + public function getTitle() { |
|
| 103 | + return $this->title; |
|
| 104 | + } |
|
| 105 | + |
|
| 106 | + /** |
|
| 107 | + * Function sets a flag in exception class to indicate that exception is already handled |
|
| 108 | + * so if it is caught again in the top level of function stack then we have to silently |
|
| 109 | + * ignore it. |
|
| 110 | + */ |
|
| 111 | + public function setHandled() { |
|
| 112 | + $this->isHandled = true; |
|
| 113 | + } |
|
| 114 | + |
|
| 115 | + /** |
|
| 116 | + * @return string returns base path of the file where exception occurred |
|
| 117 | + */ |
|
| 118 | + public function getBaseFile() { |
|
| 119 | + if (is_null($this->baseFile)) { |
|
| 120 | + $this->baseFile = basename(parent::getFile()); |
|
| 121 | + } |
|
| 122 | + |
|
| 123 | + return $this->baseFile; |
|
| 124 | + } |
|
| 125 | + |
|
| 126 | + /** |
|
| 127 | + * Name of the class of exception. |
|
| 128 | + * |
|
| 129 | + * @return string |
|
| 130 | + */ |
|
| 131 | + public function getName() { |
|
| 132 | + return get_class($this); |
|
| 133 | + } |
|
| 134 | + |
|
| 135 | + /** |
|
| 136 | + * It will return details error message if allowToShowDetailsMessage is set. |
|
| 137 | + * |
|
| 138 | + * @return string returns details error message |
|
| 139 | + */ |
|
| 140 | + public function getDetailsMessage() { |
|
| 141 | + return $this->allowToShowDetailsMessage ? $this->__toString() : ''; |
|
| 142 | + } |
|
| 143 | + |
|
| 144 | + // @TODO getTrace and getTraceAsString |
|
| 145 | 145 | } |
@@ -54,14 +54,14 @@ |
||
| 54 | 54 | // assign display message |
| 55 | 55 | $this->displayMessage = $displayMessage; |
| 56 | 56 | |
| 57 | - parent::__construct($errorMessage, (int) $code, $previous); |
|
| 57 | + parent::__construct($errorMessage, (int)$code, $previous); |
|
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | /** |
| 61 | 61 | * @return string returns file name and line number combined where exception occurred |
| 62 | 62 | */ |
| 63 | 63 | public function getFileLine() { |
| 64 | - return $this->getBaseFile() . ':' . $this->getLine(); |
|
| 64 | + return $this->getBaseFile().':'.$this->getLine(); |
|
| 65 | 65 | } |
| 66 | 66 | |
| 67 | 67 | /** |