grommunio /
grommunio-web
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * BaseRecurrence |
||
| 4 | * this class is superclass for recurrence for appointments and tasks. This class provides all |
||
| 5 | * basic features of recurrence. |
||
| 6 | */ |
||
| 7 | class BaseRecurrence |
||
| 8 | { |
||
| 9 | /** |
||
| 10 | * @var object Mapi Message Store (may be null if readonly) |
||
| 11 | */ |
||
| 12 | var $store; |
||
| 13 | |||
| 14 | /** |
||
| 15 | * @var object Mapi Message (may be null if readonly) |
||
| 16 | */ |
||
| 17 | var $message; |
||
| 18 | |||
| 19 | /** |
||
| 20 | * @var array Message Properties |
||
| 21 | */ |
||
| 22 | var $messageprops; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * @var array list of property tags |
||
| 26 | */ |
||
| 27 | var $proptags; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * @var recurrence data of this calendar item |
||
| 31 | */ |
||
| 32 | var $recur; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * @var Timezone data of this calendar item |
||
| 36 | */ |
||
| 37 | var $tz; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * Constructor |
||
| 41 | * @param resource $store MAPI Message Store Object |
||
| 42 | * @param resource $message the MAPI (appointment) message |
||
| 43 | * @param array $properties the list of MAPI properties the message has. |
||
| 44 | */ |
||
| 45 | function __construct($store, $message) |
||
| 46 | { |
||
| 47 | $this->store = $store; |
||
| 48 | |||
| 49 | if(is_array($message)) { |
||
| 50 | $this->messageprops = $message; |
||
| 51 | } else { |
||
| 52 | $this->message = $message; |
||
| 53 | $this->messageprops = mapi_getprops($this->message, $this->proptags); |
||
| 54 | } |
||
| 55 | |||
| 56 | if(isset($this->messageprops[$this->proptags["recurring_data"]])) { |
||
| 57 | // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface |
||
| 58 | if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) { |
||
| 59 | $this->getFullRecurrenceBlob(); |
||
| 60 | } |
||
| 61 | |||
| 62 | $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]); |
||
| 63 | } |
||
| 64 | if(isset($this->proptags["timezone_data"]) && isset($this->messageprops[$this->proptags["timezone_data"]])) { |
||
| 65 | $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]); |
||
| 66 | } |
||
| 67 | } |
||
| 68 | |||
| 69 | function getRecurrence() |
||
| 70 | { |
||
| 71 | return $this->recur; |
||
| 72 | } |
||
| 73 | |||
| 74 | function getFullRecurrenceBlob() |
||
| 75 | { |
||
| 76 | $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]); |
||
| 77 | |||
| 78 | $recurrBlob = ''; |
||
| 79 | $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0); |
||
| 80 | $stat = mapi_stream_stat($stream); |
||
| 81 | |||
| 82 | for ($i = 0; $i < $stat['cb']; $i += 1024) { |
||
| 83 | $recurrBlob .= mapi_stream_read($stream, 1024); |
||
| 84 | } |
||
| 85 | |||
| 86 | if (!empty($recurrBlob)) { |
||
| 87 | $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob; |
||
| 88 | } |
||
| 89 | } |
||
| 90 | |||
| 91 | /** |
||
| 92 | * Function for parsing the Recurrence value of a Calendar item. |
||
| 93 | * |
||
| 94 | * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the |
||
| 95 | * data to this function |
||
| 96 | * |
||
| 97 | * Returns a structure containing the data: |
||
| 98 | * |
||
| 99 | * type - type of recurrence: day=10, week=11, month=12, year=13 |
||
| 100 | * subtype - type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday) |
||
| 101 | * start - unix timestamp of first occurrence |
||
| 102 | * end - unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1 |
||
| 103 | * numoccur - occurrences (may be very large when there is no end data) |
||
| 104 | * |
||
| 105 | * then, for each type: |
||
| 106 | * |
||
| 107 | * Daily: |
||
| 108 | * everyn - every [everyn] days in minutes |
||
| 109 | * regen - regenerating event (like tasks) |
||
| 110 | * |
||
| 111 | * Weekly: |
||
| 112 | * everyn - every [everyn] weeks in weeks |
||
| 113 | * regen - regenerating event (like tasks) |
||
| 114 | * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
||
| 115 | * |
||
| 116 | * Monthly: |
||
| 117 | * everyn - every [everyn] months |
||
| 118 | * regen - regenerating event (like tasks) |
||
| 119 | * |
||
| 120 | * subtype 2: |
||
| 121 | * monthday - on day [monthday] of the month |
||
| 122 | * |
||
| 123 | * subtype 3: |
||
| 124 | * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
||
| 125 | * nday - on [nday]'th [weekdays] of the month |
||
| 126 | * |
||
| 127 | * Yearly: |
||
| 128 | * everyn - every [everyn] months (12, 24, 36, ...) |
||
| 129 | * month - in month [month] (although the month is encoded in minutes since the startning of the year ........) |
||
| 130 | * regen - regenerating event (like tasks) |
||
| 131 | * |
||
| 132 | * subtype 2: |
||
| 133 | * monthday - on day [monthday] of the month |
||
| 134 | * |
||
| 135 | * subtype 3: |
||
| 136 | * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc) |
||
| 137 | * nday - on [nday]'th [weekdays] of the month [month] |
||
| 138 | * @param string $rdata Binary string |
||
| 139 | * @return array recurrence data. |
||
| 140 | */ |
||
| 141 | function parseRecurrence($rdata) |
||
| 142 | { |
||
| 143 | if (strlen($rdata) < 10) { |
||
| 144 | return; |
||
| 145 | } |
||
| 146 | |||
| 147 | $ret["changed_occurrences"] = array(); |
||
| 148 | $ret["deleted_occurrences"] = array(); |
||
| 149 | |||
| 150 | $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata); |
||
| 151 | |||
| 152 | $ret["type"] = $data["rtype"]; |
||
| 153 | $ret["subtype"] = $data["rtype2"]; |
||
| 154 | $rdata = substr($rdata, 10); |
||
| 155 | |||
| 156 | switch ($data["rtype"]) |
||
| 157 | { |
||
| 158 | case 0x0a: |
||
| 159 | // Daily |
||
| 160 | if (strlen($rdata) < 12) { |
||
| 161 | return $ret; |
||
| 162 | } |
||
| 163 | |||
| 164 | $data = unpack("Vunknown/Veveryn/Vregen", $rdata); |
||
| 165 | $ret["everyn"] = $data["everyn"]; |
||
| 166 | $ret["regen"] = $data["regen"]; |
||
| 167 | |||
| 168 | switch($ret["subtype"]) |
||
| 169 | { |
||
| 170 | case 0: |
||
| 171 | $rdata = substr($rdata, 12); |
||
| 172 | break; |
||
| 173 | case 1: |
||
| 174 | $rdata = substr($rdata, 16); |
||
| 175 | break; |
||
| 176 | } |
||
| 177 | |||
| 178 | break; |
||
| 179 | |||
| 180 | case 0x0b: |
||
| 181 | // Weekly |
||
| 182 | if (strlen($rdata) < 16) { |
||
| 183 | return $ret; |
||
| 184 | } |
||
| 185 | |||
| 186 | $data = unpack("Vconst1/Veveryn/Vregen", $rdata); |
||
| 187 | $rdata = substr($rdata, 12); |
||
| 188 | |||
| 189 | $ret["everyn"] = $data["everyn"]; |
||
| 190 | $ret["regen"] = $data["regen"]; |
||
| 191 | $ret["weekdays"] = 0; |
||
| 192 | |||
| 193 | if ($data["regen"] == 0) { |
||
| 194 | $data = unpack("Vweekdays", $rdata); |
||
| 195 | $rdata = substr($rdata, 4); |
||
| 196 | |||
| 197 | $ret["weekdays"] = $data["weekdays"]; |
||
| 198 | } |
||
| 199 | break; |
||
| 200 | |||
| 201 | case 0x0c: |
||
| 202 | // Monthly |
||
| 203 | if (strlen($rdata) < 16) { |
||
| 204 | return $ret; |
||
| 205 | } |
||
| 206 | |||
| 207 | $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata); |
||
| 208 | |||
| 209 | $ret["everyn"] = $data["everyn"]; |
||
| 210 | $ret["regen"] = $data["regen"]; |
||
| 211 | |||
| 212 | if ($ret["subtype"] == 3) { |
||
| 213 | $ret["weekdays"] = $data["monthday"]; |
||
| 214 | } else { |
||
| 215 | $ret["monthday"] = $data["monthday"]; |
||
| 216 | } |
||
| 217 | |||
| 218 | $rdata = substr($rdata, 16); |
||
| 219 | |||
| 220 | if ($ret["subtype"] == 3) { |
||
| 221 | $data = unpack("Vnday", $rdata); |
||
| 222 | $ret["nday"] = $data["nday"]; |
||
| 223 | $rdata = substr($rdata, 4); |
||
| 224 | } |
||
| 225 | break; |
||
| 226 | |||
| 227 | case 0x0d: |
||
| 228 | // Yearly |
||
| 229 | if (strlen($rdata) < 16) |
||
| 230 | return $ret; |
||
| 231 | |||
| 232 | $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata); |
||
| 233 | |||
| 234 | $ret["month"] = $data["month"]; |
||
| 235 | $ret["everyn"] = $data["everyn"]; |
||
| 236 | $ret["regen"] = $data["regen"]; |
||
| 237 | |||
| 238 | if ($ret["subtype"] == 3) { |
||
| 239 | $ret["weekdays"] = $data["monthday"]; |
||
| 240 | } else { |
||
| 241 | $ret["monthday"] = $data["monthday"]; |
||
| 242 | } |
||
| 243 | |||
| 244 | $rdata = substr($rdata, 16); |
||
| 245 | |||
| 246 | if ($ret["subtype"] == 3) { |
||
| 247 | $data = unpack("Vnday", $rdata); |
||
| 248 | $ret["nday"] = $data["nday"]; |
||
| 249 | $rdata = substr($rdata, 4); |
||
| 250 | } |
||
| 251 | break; |
||
| 252 | } |
||
| 253 | |||
| 254 | if (strlen($rdata) < 16) { |
||
| 255 | return $ret; |
||
| 256 | } |
||
| 257 | |||
| 258 | $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata); |
||
| 259 | |||
| 260 | $rdata = substr($rdata, 16); |
||
| 261 | |||
| 262 | $ret["term"] = $data["term"]; |
||
| 263 | $ret["numoccur"] = $data["numoccur"]; |
||
| 264 | $ret["numexcept"] = $data["numexcept"]; |
||
| 265 | |||
| 266 | // exc_base_dates are *all* the base dates that have been either deleted or modified |
||
| 267 | $exc_base_dates = array(); |
||
| 268 | for($i = 0; $i < $ret["numexcept"]; $i++) |
||
| 269 | { |
||
| 270 | if (strlen($rdata) < 4) { |
||
| 271 | // We shouldn't arrive here, because that implies |
||
| 272 | // numexcept does not match the amount of data |
||
| 273 | // which is available for the exceptions. |
||
| 274 | return $ret; |
||
| 275 | } |
||
| 276 | $data = unpack("Vbasedate", $rdata); |
||
| 277 | $rdata = substr($rdata, 4); |
||
| 278 | $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]); |
||
| 279 | } |
||
| 280 | |||
| 281 | if (strlen($rdata) < 4) { |
||
| 282 | return $ret; |
||
| 283 | } |
||
| 284 | |||
| 285 | $data = unpack("Vnumexceptmod", $rdata); |
||
| 286 | $rdata = substr($rdata, 4); |
||
| 287 | |||
| 288 | $ret["numexceptmod"] = $data["numexceptmod"]; |
||
| 289 | |||
| 290 | // exc_changed are the base dates of *modified* occurrences. exactly what is modified |
||
| 291 | // is in the attachments *and* in the data further down this function. |
||
| 292 | $exc_changed = array(); |
||
| 293 | for($i = 0; $i < $ret["numexceptmod"]; $i++) |
||
| 294 | { |
||
| 295 | if (strlen($rdata) < 4) { |
||
| 296 | // We shouldn't arrive here, because that implies |
||
| 297 | // numexceptmod does not match the amount of data |
||
| 298 | // which is available for the exceptions. |
||
| 299 | return $ret; |
||
| 300 | } |
||
| 301 | $data = unpack("Vstartdate", $rdata); |
||
| 302 | $rdata = substr($rdata, 4); |
||
| 303 | $exc_changed[] = $this->recurDataToUnixData($data["startdate"]); |
||
| 304 | } |
||
| 305 | |||
| 306 | if (strlen($rdata) < 8) { |
||
| 307 | return $ret; |
||
| 308 | } |
||
| 309 | |||
| 310 | $data = unpack("Vstart/Vend", $rdata); |
||
| 311 | $rdata = substr($rdata, 8); |
||
| 312 | |||
| 313 | $ret["start"] = $this->recurDataToUnixData($data["start"]); |
||
| 314 | $ret["end"] = $this->recurDataToUnixData($data["end"]); |
||
| 315 | |||
| 316 | // this is where task recurrence stop |
||
| 317 | if (strlen($rdata) < 16) { |
||
| 318 | return $ret; |
||
| 319 | } |
||
| 320 | |||
| 321 | $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata); |
||
| 322 | $rdata = substr($rdata, 16); |
||
| 323 | |||
| 324 | $ret["startocc"] = $data["startmin"]; |
||
| 325 | $ret["endocc"] = $data["endmin"]; |
||
| 326 | $writerversion = $data["writerversion"]; |
||
| 327 | |||
| 328 | $data = unpack("vnumber", $rdata); |
||
| 329 | $rdata = substr($rdata, 2); |
||
| 330 | |||
| 331 | $nexceptions = $data["number"]; |
||
| 332 | $exc_changed_details = array(); |
||
| 333 | |||
| 334 | // Parse n modified exceptions |
||
| 335 | for($i=0;$i<$nexceptions;$i++) |
||
| 336 | { |
||
| 337 | $item = array(); |
||
| 338 | |||
| 339 | // Get exception startdate, enddate and basedate (the date at which the occurrence would have started) |
||
| 340 | $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata); |
||
| 341 | $rdata = substr($rdata, 12); |
||
| 342 | |||
| 343 | // Convert recurtimestamp to unix timestamp |
||
| 344 | $startdate = $this->recurDataToUnixData($data["startdate"]); |
||
| 345 | $enddate = $this->recurDataToUnixData($data["enddate"]); |
||
| 346 | $basedate = $this->recurDataToUnixData($data["basedate"]); |
||
| 347 | |||
| 348 | // Set the right properties |
||
| 349 | $item["basedate"] = $this->dayStartOf($basedate); |
||
| 350 | $item["start"] = $startdate; |
||
| 351 | $item["end"] = $enddate; |
||
| 352 | |||
| 353 | $data = unpack("vbitmask", $rdata); |
||
| 354 | $rdata = substr($rdata, 2); |
||
| 355 | $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions |
||
| 356 | |||
| 357 | // Bitmask to verify what properties are changed |
||
| 358 | $bitmask = $data["bitmask"]; |
||
| 359 | |||
| 360 | // ARO_SUBJECT: 0x0001 |
||
| 361 | // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject |
||
| 362 | if(($bitmask &(1 << 0))) { |
||
| 363 | $data = unpack("vnull_length/vlength", $rdata); |
||
| 364 | $rdata = substr($rdata, 4); |
||
| 365 | |||
| 366 | $length = $data["length"]; |
||
| 367 | $item["subject"] = ""; // Normalized subject |
||
| 368 | for($j = 0; $j < $length && strlen($rdata); $j++) |
||
| 369 | { |
||
| 370 | $data = unpack("Cchar", $rdata); |
||
| 371 | $rdata = substr($rdata, 1); |
||
| 372 | |||
| 373 | $item["subject"] .= chr($data["char"]); |
||
| 374 | } |
||
| 375 | } |
||
| 376 | |||
| 377 | // ARO_MEETINGTYPE: 0x0002 |
||
| 378 | if(($bitmask &(1 << 1))) { |
||
| 379 | $rdata = substr($rdata, 4); |
||
| 380 | // Attendees modified: no data here (only in attachment) |
||
| 381 | } |
||
| 382 | |||
| 383 | // ARO_REMINDERDELTA: 0x0004 |
||
| 384 | // Look for field: ReminderDelta (4b) |
||
| 385 | if(($bitmask &(1 << 2))) { |
||
| 386 | $data = unpack("Vremind_before", $rdata); |
||
| 387 | $rdata = substr($rdata, 4); |
||
| 388 | |||
| 389 | $item["remind_before"] = $data["remind_before"]; |
||
| 390 | } |
||
| 391 | |||
| 392 | // ARO_REMINDER: 0x0008 |
||
| 393 | // Look field: ReminderSet (4b) |
||
| 394 | if(($bitmask &(1 << 3))) { |
||
| 395 | $data = unpack("Vreminder_set", $rdata); |
||
| 396 | $rdata = substr($rdata, 4); |
||
| 397 | |||
| 398 | $item["reminder_set"] = $data["reminder_set"]; |
||
| 399 | } |
||
| 400 | |||
| 401 | // ARO_LOCATION: 0x0010 |
||
| 402 | // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location |
||
| 403 | // Similar to ARO_SUBJECT above. |
||
| 404 | if(($bitmask &(1 << 4))) { |
||
| 405 | $data = unpack("vnull_length/vlength", $rdata); |
||
| 406 | $rdata = substr($rdata, 4); |
||
| 407 | |||
| 408 | $item["location"] = ""; |
||
| 409 | |||
| 410 | $length = $data["length"]; |
||
| 411 | $data = substr($rdata, 0, $length); |
||
| 412 | $rdata = substr($rdata, $length); |
||
| 413 | |||
| 414 | $item["location"] .= $data; |
||
| 415 | } |
||
| 416 | |||
| 417 | // ARO_BUSYSTATUS: 0x0020 |
||
| 418 | // Look for field: BusyStatus (4b) |
||
| 419 | if(($bitmask &(1 << 5))) { |
||
| 420 | $data = unpack("Vbusystatus", $rdata); |
||
| 421 | $rdata = substr($rdata, 4); |
||
| 422 | |||
| 423 | $item["busystatus"] = $data["busystatus"]; |
||
| 424 | } |
||
| 425 | |||
| 426 | // ARO_ATTACHMENT: 0x0040 |
||
| 427 | if(($bitmask &(1 << 6))) { |
||
| 428 | // no data: RESERVED |
||
| 429 | $rdata = substr($rdata, 4); |
||
| 430 | } |
||
| 431 | |||
| 432 | // ARO_SUBTYPE: 0x0080 |
||
| 433 | // Look for field: SubType (4b). Determines whether it is an allday event. |
||
| 434 | if(($bitmask &(1 << 7))) { |
||
| 435 | $data = unpack("Vallday", $rdata); |
||
| 436 | $rdata = substr($rdata, 4); |
||
| 437 | |||
| 438 | $item["alldayevent"] = $data["allday"]; |
||
| 439 | } |
||
| 440 | |||
| 441 | // ARO_APPTCOLOR: 0x0100 |
||
| 442 | // Look for field: AppointmentColor (4b) |
||
| 443 | if(($bitmask &(1 << 8))) { |
||
| 444 | $data = unpack("Vlabel", $rdata); |
||
| 445 | $rdata = substr($rdata, 4); |
||
| 446 | |||
| 447 | $item["label"] = $data["label"]; |
||
| 448 | } |
||
| 449 | |||
| 450 | // ARO_EXCEPTIONAL_BODY: 0x0200 |
||
| 451 | if(($bitmask &(1 << 9))) { |
||
| 452 | // Notes or Attachments modified: no data here (only in attachment) |
||
| 453 | } |
||
| 454 | |||
| 455 | array_push($exc_changed_details, $item); |
||
| 456 | } |
||
| 457 | |||
| 458 | /** |
||
| 459 | * We now have $exc_changed, $exc_base_dates and $exc_changed_details |
||
| 460 | * We will ignore $exc_changed, as this information is available in $exc_changed_details |
||
| 461 | * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item |
||
| 462 | * has been deleted. |
||
| 463 | */ |
||
| 464 | |||
| 465 | // Find deleted occurrences |
||
| 466 | $deleted_occurrences = array(); |
||
| 467 | |||
| 468 | foreach($exc_base_dates as $base_date) { |
||
| 469 | $found = false; |
||
| 470 | |||
| 471 | foreach($exc_changed_details as $details) { |
||
| 472 | if($details["basedate"] == $base_date) { |
||
| 473 | $found = true; |
||
| 474 | break; |
||
| 475 | } |
||
| 476 | } |
||
| 477 | if(! $found) { |
||
| 478 | // item was not in exc_changed_details, so it must be deleted |
||
| 479 | $deleted_occurrences[] = $base_date; |
||
| 480 | } |
||
| 481 | } |
||
| 482 | |||
| 483 | $ret["deleted_occurrences"] = $deleted_occurrences; |
||
| 484 | $ret["changed_occurrences"] = $exc_changed_details; |
||
| 485 | |||
| 486 | // enough data for normal exception (no extended data) |
||
| 487 | if (strlen($rdata) < 16) { |
||
| 488 | return $ret; |
||
| 489 | } |
||
| 490 | |||
| 491 | $data = unpack("Vreservedsize", $rdata); |
||
| 492 | $rdata = substr($rdata, 4 + $data["reservedsize"]); |
||
| 493 | |||
| 494 | for($i=0;$i<$nexceptions;$i++) |
||
| 495 | { |
||
| 496 | // subject and location in ucs-2 to utf-8 |
||
| 497 | if ($writerversion >= 0x3009) { |
||
| 498 | $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4 |
||
| 499 | $rdata = substr($rdata, 4 + $data["size"]); |
||
| 500 | } |
||
| 501 | |||
| 502 | $data = unpack("Vreservedsize", $rdata); |
||
| 503 | $rdata = substr($rdata, 4 + $data["reservedsize"]); |
||
| 504 | |||
| 505 | // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10) |
||
| 506 | if ($exc_changed_details[$i]["bitmask"] & 0x11) { |
||
| 507 | $data = unpack("Vstart/Vend/Vorig", $rdata); |
||
| 508 | $rdata = substr($rdata, 4 * 3); |
||
| 509 | |||
| 510 | $exc_changed_details[$i]["ex_start_datetime"] = $data["start"]; |
||
| 511 | $exc_changed_details[$i]["ex_end_datetime"] = $data["end"]; |
||
| 512 | $exc_changed_details[$i]["ex_orig_date"] = $data["orig"]; |
||
| 513 | } |
||
| 514 | |||
| 515 | // ARO_SUBJECT |
||
| 516 | if ($exc_changed_details[$i]["bitmask"] & 0x01) { |
||
| 517 | // decode ucs2 string to utf-8 |
||
| 518 | $data = unpack("vlength", $rdata); |
||
| 519 | $rdata = substr($rdata, 2); |
||
| 520 | $length = $data["length"]; |
||
| 521 | $data = substr($rdata, 0, $length * 2); |
||
| 522 | $rdata = substr($rdata, $length * 2); |
||
| 523 | $subject = iconv("UCS-2LE", "UTF-8", $data); |
||
| 524 | // replace subject with unicode subject |
||
| 525 | $exc_changed_details[$i]["subject"] = $subject; |
||
| 526 | } |
||
| 527 | |||
| 528 | // ARO_LOCATION |
||
| 529 | if ($exc_changed_details[$i]["bitmask"] & 0x10) { |
||
| 530 | // decode ucs2 string to utf-8 |
||
| 531 | $data = unpack("vlength", $rdata); |
||
| 532 | $rdata = substr($rdata, 2); |
||
| 533 | $length = $data["length"]; |
||
| 534 | $data = substr($rdata, 0, $length * 2); |
||
| 535 | $rdata = substr($rdata, $length * 2); |
||
| 536 | $location = iconv("UCS-2LE", "UTF-8", $data); |
||
| 537 | // replace subject with unicode subject |
||
| 538 | $exc_changed_details[$i]["location"] = $location; |
||
| 539 | } |
||
| 540 | |||
| 541 | // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10) |
||
| 542 | if ($exc_changed_details[$i]["bitmask"] & 0x11) { |
||
| 543 | $data = unpack("Vreservedsize", $rdata); |
||
| 544 | $rdata = substr($rdata, 4 + $data["reservedsize"]); |
||
| 545 | } |
||
| 546 | } |
||
| 547 | |||
| 548 | // update with extended data |
||
| 549 | $ret["changed_occurrences"] = $exc_changed_details; |
||
| 550 | |||
| 551 | return $ret; |
||
| 552 | } |
||
| 553 | |||
| 554 | /** |
||
| 555 | * Saves the recurrence data to the recurrence property |
||
| 556 | * @param array $properties the recurrence data. |
||
| 557 | * @return string binary string |
||
| 558 | */ |
||
| 559 | function saveRecurrence() |
||
| 560 | { |
||
| 561 | // Only save if a message was passed |
||
| 562 | if(!isset($this->message)) |
||
| 563 | return; |
||
| 564 | |||
| 565 | // Abort if no recurrence was set |
||
| 566 | if(!isset($this->recur["type"]) && !isset($this->recur["subtype"])) { |
||
| 567 | return; |
||
| 568 | } |
||
| 569 | |||
| 570 | if(!isset($this->recur["start"]) && !isset($this->recur["end"])) { |
||
| 571 | return; |
||
| 572 | } |
||
| 573 | |||
| 574 | if(!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) { |
||
| 575 | return; |
||
| 576 | } |
||
| 577 | |||
| 578 | $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]); |
||
| 579 | |||
| 580 | $weekstart = 1; //monday |
||
| 581 | $forwardcount = 0; |
||
| 582 | $restocc = 0; |
||
| 583 | $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); //0 (for Sunday) through 6 (for Saturday) |
||
| 584 | |||
| 585 | $term = (int) $this->recur["type"]; |
||
| 586 | switch($term) |
||
| 587 | { |
||
| 588 | case 0x0A: |
||
| 589 | // Daily |
||
| 590 | if(!isset($this->recur["everyn"])) { |
||
| 591 | return; |
||
| 592 | } |
||
| 593 | |||
| 594 | if($this->recur["subtype"] == 1) { |
||
| 595 | |||
| 596 | // Daily every workday |
||
| 597 | $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E); |
||
| 598 | } else { |
||
| 599 | // Daily every N days (everyN in minutes) |
||
| 600 | |||
| 601 | $everyn = ((int) $this->recur["everyn"]) / 1440; |
||
| 602 | |||
| 603 | // Calc first occ |
||
| 604 | $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]); |
||
| 605 | |||
| 606 | $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0); |
||
| 607 | } |
||
| 608 | break; |
||
| 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 | $daycount++; |
||
| 632 | } |
||
| 633 | } |
||
| 634 | |||
| 635 | // $dayskip is the number of days to skip from the startdate until the first occurrence |
||
| 636 | // $daycount is the number of days per week that an occurrence occurs |
||
| 637 | |||
| 638 | $weekskip = 0; |
||
| 639 | if(($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek+$dayskip) > 6) |
||
| 640 | $weekskip = 1; |
||
| 641 | |||
| 642 | // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
||
| 643 | // remaining occurrences based on the start of the recurrence. |
||
| 644 | if (((int) $this->recur["term"]) == 0x22) { |
||
| 645 | // $weekskip is the amount of weeks to skip from the startdate before the first occurrence |
||
| 646 | // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that |
||
| 647 | // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over |
||
| 648 | // (eg when numoccur = 2, and daycount = 1) |
||
| 649 | $forwardcount = floor( (int) ($this->recur["numoccur"] -1 ) / $daycount); |
||
| 650 | |||
| 651 | // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one |
||
| 652 | // for the occurrence on the first day |
||
| 653 | $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount*$daycount) - 1; |
||
| 654 | |||
| 655 | // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence |
||
| 656 | $forwardcount *= (int) $this->recur["everyn"]; |
||
| 657 | } |
||
| 658 | |||
| 659 | // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week) |
||
| 660 | $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24*60*60)+ ($weekskip *(((int) $this->recur["everyn"]) - 1) * 7 * 24*60*60); |
||
| 661 | } |
||
| 662 | |||
| 663 | // Calc first occ |
||
| 664 | $firstocc = ($this->unixDataToRecurData($this->recur["start"]) ) % ( ((int) $this->recur["everyn"]) * 7 * 24 * 60); |
||
| 665 | |||
| 666 | $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60; |
||
| 667 | |||
| 668 | if ($this->recur["regen"]) |
||
| 669 | $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1); |
||
| 670 | else |
||
| 671 | $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]); |
||
| 672 | break; |
||
| 673 | case 0x0C: |
||
| 674 | // Monthly |
||
| 675 | case 0x0D: |
||
| 676 | // Yearly |
||
| 677 | if(!isset($this->recur["everyn"])) { |
||
| 678 | return; |
||
| 679 | } |
||
| 680 | if($term == 0x0D /*yearly*/ && !isset($this->recur["month"])) { |
||
| 681 | return; |
||
| 682 | } |
||
| 683 | |||
| 684 | if($term == 0x0C /*monthly*/) { |
||
| 685 | $everyn = (int) $this->recur["everyn"]; |
||
| 686 | } else { |
||
| 687 | $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12; |
||
| 688 | } |
||
| 689 | |||
| 690 | // Get montday/month/year of original start |
||
| 691 | $curmonthday = gmdate("j", (int) $this->recur["start"] ); |
||
| 692 | $curyear = gmdate("Y", (int) $this->recur["start"] ); |
||
| 693 | $curmonth = gmdate("n", (int) $this->recur["start"] ); |
||
| 694 | |||
| 695 | // Check if the recurrence ends after a number of occurrences, in that case we must calculate the |
||
| 696 | // remaining occurrences based on the start of the recurrence. |
||
| 697 | if (((int) $this->recur["term"]) == 0x22) { |
||
| 698 | // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus |
||
| 699 | // one to make sure there are always at least one occurrence left) |
||
| 700 | $forwardcount = ((((int) $this->recur["numoccur"])-1) * $everyn ); |
||
| 701 | } |
||
| 702 | |||
| 703 | // Get month for yearly on D'th day of month M |
||
| 704 | if($term == 0x0D /*yearly*/) { |
||
| 705 | $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 *29)) + 1; // 1=jan, 2=feb, eg |
||
| 706 | } |
||
| 707 | |||
| 708 | switch((int) $this->recur["subtype"]) |
||
| 709 | { |
||
| 710 | // on D day of every M month |
||
| 711 | case 2: |
||
| 712 | if(!isset($this->recur["monthday"])) { |
||
| 713 | return; |
||
| 714 | } |
||
| 715 | // Recalc startdate |
||
| 716 | |||
| 717 | // Set on the right begin day |
||
| 718 | |||
| 719 | // Go the beginning of the month |
||
| 720 | $this->recur["start"] -= ($curmonthday-1) * 24*60*60; |
||
| 721 | // Go the the correct month day |
||
| 722 | $this->recur["start"] += (((int) $this->recur["monthday"])-1) * 24*60*60; |
||
| 723 | |||
| 724 | // If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence |
||
| 725 | if ( ($term == 0x0C /*monthly*/ && ((int) $this->recur["monthday"]) < $curmonthday) || |
||
| 726 | ($term == 0x0D /*yearly*/ && ( $selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)) )) |
||
| 727 | { |
||
| 728 | if($term == 0x0D /*yearly*/) { |
||
| 729 | if ($curmonth > $selmonth) {//go to next occurrence in 'everyn' months minus difference in first occurrence and original date |
||
| 730 | $count = $everyn - ($curmonth - $selmonth); |
||
| 731 | } else if($curmonth < $selmonth) {//go to next occurrence upto difference in first occurrence and original date |
||
| 732 | $count = $selmonth - $curmonth; |
||
| 733 | } else { |
||
| 734 | // Go to next occurrence while recurrence start date is greater than occurrence date but within same month |
||
| 735 | if (((int) $this->recur["monthday"]) < $curmonthday) { |
||
| 736 | $count = $everyn; |
||
| 737 | } |
||
| 738 | } |
||
| 739 | } else { |
||
| 740 | $count = $everyn; // Monthly, go to next occurrence in 'everyn' months |
||
| 741 | } |
||
| 742 | |||
| 743 | // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days |
||
| 744 | for($i=0; $i < $count; $i++) { |
||
| 745 | $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth); |
||
| 746 | |||
| 747 | if($curmonth == 12) { |
||
| 748 | $curyear++; |
||
| 749 | $curmonth = 0; |
||
| 750 | } |
||
| 751 | $curmonth++; |
||
| 752 | } |
||
| 753 | } |
||
| 754 | |||
| 755 | // "start" is now pointing to the first occurrence, except that it will overshoot if the |
||
| 756 | // month in which it occurs has less days than specified as the day of the month. So 31st |
||
| 757 | // of each month will overshoot in february (29 days). We compensate for that by checking |
||
| 758 | // if the day of the month we got is wrong, and then back up to the last day of the previous |
||
| 759 | // month. |
||
| 760 | if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 && |
||
| 761 | gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"])) |
||
| 762 | { |
||
| 763 | $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 *60; |
||
| 764 | } |
||
| 765 | |||
| 766 | // "start" is now the first occurrence |
||
| 767 | |||
| 768 | if($term == 0x0C /*monthly*/) { |
||
| 769 | // Calc first occ |
||
| 770 | $monthIndex = ((((12%$everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn; |
||
| 771 | |||
| 772 | $firstocc = 0; |
||
| 773 | for($i=0; $i < $monthIndex; $i++) { |
||
| 774 | $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60; |
||
| 775 | } |
||
| 776 | |||
| 777 | $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
||
| 778 | } else { |
||
| 779 | // Calc first occ |
||
| 780 | $firstocc = 0; |
||
| 781 | $monthIndex = (int) gmdate("n", $this->recur["start"]); |
||
| 782 | for($i=1; $i < $monthIndex; $i++) { |
||
| 783 | $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60; |
||
| 784 | } |
||
| 785 | |||
| 786 | $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]); |
||
| 787 | } |
||
| 788 | break; |
||
| 789 | |||
| 790 | case 3: |
||
| 791 | // monthly: on Nth weekday of every M month |
||
| 792 | // yearly: on Nth weekday of M month |
||
| 793 | if(!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) { |
||
| 794 | return; |
||
| 795 | } |
||
| 796 | |||
| 797 | $weekdays = (int) $this->recur["weekdays"]; |
||
| 798 | $nday = (int) $this->recur["nday"]; |
||
| 799 | |||
| 800 | // Calc startdate |
||
| 801 | $monthbegindow = (int) $this->recur["start"]; |
||
| 802 | |||
| 803 | if($nday == 5) { |
||
| 804 | // Set date on the last day of the last month |
||
| 805 | $monthbegindow += (gmdate("t", $monthbegindow ) - gmdate("j", $monthbegindow )) * 24 * 60 * 60; |
||
| 806 | } else { |
||
| 807 | // Set on the first day of the month |
||
| 808 | $monthbegindow -= ((gmdate("j", $monthbegindow )-1) * 24 * 60 * 60); |
||
| 809 | } |
||
| 810 | |||
| 811 | if($term == 0x0D /*yearly*/) { |
||
| 812 | // Set on right month |
||
| 813 | if($selmonth < $curmonth) |
||
| 814 | $tmp = 12 - $curmonth + $selmonth; |
||
| 815 | else |
||
| 816 | $tmp = ($selmonth - $curmonth); |
||
| 817 | |||
| 818 | for($i=0; $i < $tmp; $i++) { |
||
| 819 | $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); |
||
| 820 | |||
| 821 | if($curmonth == 12) { |
||
| 822 | $curyear++; |
||
| 823 | $curmonth = 0; |
||
| 824 | } |
||
| 825 | $curmonth++; |
||
| 826 | } |
||
| 827 | |||
| 828 | } else { |
||
| 829 | // Check or you exist in the right month |
||
| 830 | |||
| 831 | $dayofweek = gmdate("w", $monthbegindow); |
||
| 832 | for($i = 0; $i < 7; $i++) { |
||
| 833 | if($nday == 5 && (($dayofweek-$i)%7 >= 0) && (1<<( ($dayofweek-$i)%7) ) & $weekdays) { |
||
| 834 | $day = gmdate("j", $monthbegindow) - $i; |
||
| 835 | break; |
||
| 836 | } else if($nday != 5 && (1<<( ($dayofweek+$i)%7) ) & $weekdays) { |
||
| 837 | $day = (($nday-1)*7) + ($i+1); |
||
| 838 | break; |
||
| 839 | } |
||
| 840 | } |
||
| 841 | |||
| 842 | // Goto the next X month |
||
| 843 | if(isset($day) && ($day < gmdate("j", (int) $this->recur["start"])) ) { |
||
| 844 | if($nday == 5) { |
||
| 845 | $monthbegindow += 24 * 60 * 60; |
||
| 846 | if($curmonth == 12) { |
||
| 847 | $curyear++; |
||
| 848 | $curmonth = 0; |
||
| 849 | } |
||
| 850 | $curmonth++; |
||
| 851 | } |
||
| 852 | |||
| 853 | for($i=0; $i < $everyn; $i++) { |
||
| 854 | $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth); |
||
| 855 | |||
| 856 | if($curmonth == 12) { |
||
| 857 | $curyear++; |
||
| 858 | $curmonth = 0; |
||
| 859 | } |
||
| 860 | $curmonth++; |
||
| 861 | } |
||
| 862 | |||
| 863 | if($nday == 5) { |
||
| 864 | $monthbegindow -= 24 * 60 * 60; |
||
| 865 | } |
||
| 866 | } |
||
| 867 | } |
||
| 868 | |||
| 869 | //FIXME: weekstart? |
||
| 870 | |||
| 871 | $day = 0; |
||
| 872 | // Set start on the right day |
||
| 873 | $dayofweek = gmdate("w", $monthbegindow); |
||
| 874 | for($i = 0; $i < 7; $i++) { |
||
| 875 | if($nday == 5 && (($dayofweek-$i)%7) >= 0&& (1<<(($dayofweek-$i)%7) ) & $weekdays) { |
||
| 876 | $day = $i; |
||
| 877 | break; |
||
| 878 | } else if($nday != 5 && (1<<( ($dayofweek+$i)%7) ) & $weekdays) { |
||
| 879 | $day = ($nday - 1) * 7 + ($i+1); |
||
| 880 | break; |
||
| 881 | } |
||
| 882 | } |
||
| 883 | if($nday == 5) |
||
| 884 | $monthbegindow -= $day * 24 * 60 *60; |
||
| 885 | else |
||
| 886 | $monthbegindow += ($day-1) * 24 * 60 *60; |
||
| 887 | |||
| 888 | $firstocc = 0; |
||
| 889 | |||
| 890 | if($term == 0x0C /*monthly*/) { |
||
| 891 | // Calc first occ |
||
| 892 | $monthIndex = ((((12%$everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn; |
||
| 893 | |||
| 894 | for($i=0; $i < $monthIndex; $i++) { |
||
| 895 | $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60; |
||
| 896 | } |
||
| 897 | |||
| 898 | $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
||
| 899 | } else { |
||
| 900 | // Calc first occ |
||
| 901 | $monthIndex = (int) gmdate("n", $this->recur["start"]); |
||
| 902 | |||
| 903 | for($i=1; $i < $monthIndex; $i++) { |
||
| 904 | $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60; |
||
| 905 | } |
||
| 906 | |||
| 907 | $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday); |
||
| 908 | } |
||
| 909 | break; |
||
| 910 | } |
||
| 911 | break; |
||
| 912 | |||
| 913 | |||
| 914 | |||
| 915 | } |
||
| 916 | |||
| 917 | if(!isset($this->recur["term"])) { |
||
| 918 | return; |
||
| 919 | } |
||
| 920 | |||
| 921 | // Terminate |
||
| 922 | $term = (int) $this->recur["term"]; |
||
| 923 | $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00); |
||
| 924 | |||
| 925 | switch($term) |
||
| 926 | { |
||
| 927 | // After the given enddate |
||
| 928 | case 0x21: |
||
| 929 | $rdata .= pack("V", 10); |
||
| 930 | break; |
||
| 931 | // After a number of times |
||
| 932 | case 0x22: |
||
| 933 | if(!isset($this->recur["numoccur"])) { |
||
| 934 | return; |
||
| 935 | } |
||
| 936 | |||
| 937 | $rdata .= pack("V", (int) $this->recur["numoccur"]); |
||
| 938 | break; |
||
| 939 | // Never ends |
||
| 940 | case 0x23: |
||
| 941 | $rdata .= pack("V", 0); |
||
| 942 | break; |
||
| 943 | } |
||
| 944 | |||
| 945 | // Strange little thing for the recurrence type "every workday" |
||
| 946 | if(((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) { |
||
| 947 | $rdata .= pack("V", 1); |
||
| 948 | } else { // Other recurrences |
||
| 949 | $rdata .= pack("V", 0); |
||
| 950 | } |
||
| 951 | |||
| 952 | // Exception data |
||
| 953 | |||
| 954 | // Get all exceptions |
||
| 955 | $deleted_items = $this->recur["deleted_occurrences"]; |
||
| 956 | $changed_items = $this->recur["changed_occurrences"]; |
||
| 957 | |||
| 958 | // Merge deleted and changed items into one list |
||
| 959 | $items = $deleted_items; |
||
| 960 | |||
| 961 | foreach($changed_items as $changed_item) |
||
| 962 | array_push($items, $changed_item["basedate"]); |
||
| 963 | |||
| 964 | sort($items); |
||
| 965 | |||
| 966 | // Add the merged list in to the rdata |
||
| 967 | $rdata .= pack("V", count($items)); |
||
| 968 | foreach($items as $item) |
||
| 969 | $rdata .= pack("V", $this->unixDataToRecurData($item)); |
||
| 970 | |||
| 971 | // Loop through the changed exceptions (not deleted) |
||
| 972 | $rdata .= pack("V", count($changed_items)); |
||
| 973 | $items = array(); |
||
| 974 | |||
| 975 | foreach($changed_items as $changed_item) |
||
| 976 | { |
||
| 977 | $items[] = $this->dayStartOf($changed_item["start"]); |
||
| 978 | } |
||
| 979 | |||
| 980 | sort($items); |
||
| 981 | |||
| 982 | // Add the changed items list int the rdata |
||
| 983 | foreach($items as $item) |
||
| 984 | $rdata .= pack("V", $this->unixDataToRecurData($item)); |
||
| 985 | |||
| 986 | // Set start date |
||
| 987 | $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"])); |
||
| 988 | |||
| 989 | // Set enddate |
||
| 990 | switch($term) |
||
| 991 | { |
||
| 992 | // After the given enddate |
||
| 993 | case 0x21: |
||
| 994 | $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"])); |
||
| 995 | break; |
||
| 996 | // After a number of times |
||
| 997 | case 0x22: |
||
| 998 | // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour |
||
| 999 | $occenddate = (int) $this->recur["start"]; |
||
| 1000 | |||
| 1001 | switch((int) $this->recur["type"]) { |
||
| 1002 | case 0x0A: //daily |
||
| 1003 | |||
| 1004 | if($this->recur["subtype"] == 1) { |
||
| 1005 | // Daily every workday |
||
| 1006 | $restocc = (int) $this->recur["numoccur"]; |
||
| 1007 | |||
| 1008 | // Get starting weekday |
||
| 1009 | $nowtime = $this->gmtime($occenddate); |
||
| 1010 | $j = $nowtime["tm_wday"]; |
||
| 1011 | |||
| 1012 | while(1) |
||
| 1013 | { |
||
| 1014 | if(($j%7) > 0 && ($j%7)<6 ) { |
||
| 1015 | $restocc--; |
||
| 1016 | } |
||
| 1017 | |||
| 1018 | $j++; |
||
| 1019 | |||
| 1020 | if($restocc <= 0) |
||
| 1021 | break; |
||
| 1022 | |||
| 1023 | $occenddate += 24*60*60; |
||
| 1024 | } |
||
| 1025 | |||
| 1026 | } else { |
||
| 1027 | // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence) |
||
| 1028 | $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"]-1))); |
||
| 1029 | } |
||
| 1030 | break; |
||
| 1031 | case 0x0B: //weekly |
||
| 1032 | // Needed values |
||
| 1033 | // $forwardcount - number of weeks we can skip forward |
||
| 1034 | // $restocc - number of remaining occurrences after the week skip |
||
| 1035 | |||
| 1036 | // Add the weeks till the last item |
||
| 1037 | $occenddate+=($forwardcount*7*24*60*60); |
||
| 1038 | |||
| 1039 | $dayofweek = gmdate("w", $occenddate); |
||
| 1040 | |||
| 1041 | // Loop through the last occurrences until we have had them all |
||
| 1042 | for($j = 1; $restocc>0; $j++) |
||
| 1043 | { |
||
| 1044 | // Jump to the next week (which may be N weeks away) when going over the week boundary |
||
| 1045 | if((($dayofweek+$j)%7) == $weekstart) |
||
| 1046 | $occenddate += (((int) $this->recur["everyn"])-1) * 7 * 24*60*60; |
||
| 1047 | |||
| 1048 | // If this is a matching day, once less occurrence to process |
||
| 1049 | if(((int) $this->recur["weekdays"]) & (1<<(($dayofweek+$j)%7)) ) { |
||
| 1050 | $restocc--; |
||
| 1051 | } |
||
| 1052 | |||
| 1053 | // Next day |
||
| 1054 | $occenddate += 24*60*60; |
||
| 1055 | } |
||
| 1056 | |||
| 1057 | break; |
||
| 1058 | case 0x0C: //monthly |
||
| 1059 | case 0x0D: //yearly |
||
| 1060 | |||
| 1061 | $curyear = gmdate("Y", (int) $this->recur["start"] ); |
||
| 1062 | $curmonth = gmdate("n", (int) $this->recur["start"] ); |
||
| 1063 | // $forwardcount = months |
||
| 1064 | |||
| 1065 | switch((int) $this->recur["subtype"]) |
||
| 1066 | { |
||
| 1067 | case 2: // on D day of every M month |
||
| 1068 | while($forwardcount > 0) |
||
| 1069 | { |
||
| 1070 | $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
||
| 1071 | |||
| 1072 | if($curmonth >=12) { |
||
| 1073 | $curmonth = 1; |
||
| 1074 | $curyear++; |
||
| 1075 | } else { |
||
| 1076 | $curmonth++; |
||
| 1077 | } |
||
| 1078 | $forwardcount--; |
||
| 1079 | } |
||
| 1080 | |||
| 1081 | // compensation between 28 and 31 |
||
| 1082 | if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 && |
||
| 1083 | gmdate("j", $occenddate) < ((int) $this->recur["monthday"])) |
||
| 1084 | { |
||
| 1085 | if(gmdate("j", $occenddate) < 28) |
||
| 1086 | $occenddate -= gmdate("j", $occenddate) * 24 * 60 *60; |
||
| 1087 | else |
||
| 1088 | $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 *60; |
||
| 1089 | } |
||
| 1090 | |||
| 1091 | |||
| 1092 | break; |
||
| 1093 | case 3: // on Nth weekday of every M month |
||
| 1094 | $nday = (int) $this->recur["nday"]; //1 tot 5 |
||
| 1095 | $weekdays = (int) $this->recur["weekdays"]; |
||
| 1096 | |||
| 1097 | |||
| 1098 | while($forwardcount > 0) |
||
| 1099 | { |
||
| 1100 | $occenddate += $this->getMonthInSeconds($curyear, $curmonth); |
||
| 1101 | if($curmonth >=12) { |
||
| 1102 | $curmonth = 1; |
||
| 1103 | $curyear++; |
||
| 1104 | } else { |
||
| 1105 | $curmonth++; |
||
| 1106 | } |
||
| 1107 | |||
| 1108 | $forwardcount--; |
||
| 1109 | } |
||
| 1110 | |||
| 1111 | if($nday == 5) { |
||
| 1112 | // Set date on the last day of the last month |
||
| 1113 | $occenddate += (gmdate("t", $occenddate ) - gmdate("j", $occenddate )) * 24 * 60 * 60; |
||
| 1114 | } else { |
||
| 1115 | // Set date on the first day of the last month |
||
| 1116 | $occenddate -= (gmdate("j", $occenddate )-1) * 24 * 60 * 60; |
||
| 1117 | } |
||
| 1118 | |||
| 1119 | for($i = 0; $i < 7; $i++) { |
||
| 1120 | if( $nday == 5 && (1<<( (gmdate("w", $occenddate)-$i)%7) ) & $weekdays) { |
||
| 1121 | $occenddate -= $i * 24 * 60 * 60; |
||
| 1122 | break; |
||
| 1123 | } else if($nday != 5 && (1<<( (gmdate("w", $occenddate)+$i)%7) ) & $weekdays) { |
||
| 1124 | $occenddate += ($i + (($nday-1) *7)) * 24 * 60 * 60; |
||
| 1125 | break; |
||
| 1126 | } |
||
| 1127 | } |
||
| 1128 | |||
| 1129 | break; //case 3: |
||
| 1130 | } |
||
| 1131 | |||
| 1132 | break; |
||
| 1133 | |||
| 1134 | } |
||
| 1135 | |||
| 1136 | if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX) |
||
| 1137 | $occenddate = PHP_INT_MAX; |
||
| 1138 | |||
| 1139 | $this->recur["end"] = $occenddate; |
||
| 1140 | |||
| 1141 | $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]) ); |
||
| 1142 | break; |
||
| 1143 | // Never ends |
||
| 1144 | case 0x23: |
||
| 1145 | default: |
||
| 1146 | $this->recur["end"] = 0x7fffffff; // max date -> 2038 |
||
| 1147 | $rdata .= pack("V", 0x5AE980DF); |
||
| 1148 | break; |
||
| 1149 | } |
||
| 1150 | |||
| 1151 | // UTC date |
||
| 1152 | $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]); |
||
| 1153 | $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]); |
||
| 1154 | |||
| 1155 | //utc date+time |
||
| 1156 | $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"])*60) : $utcstart; |
||
| 1157 | $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart; |
||
| 1158 | |||
| 1159 | // update reminder time |
||
| 1160 | mapi_setprops($this->message, Array($this->proptags["reminder_time"] => $utcfirstoccstartdatetime )); |
||
| 1161 | |||
| 1162 | // update first occurrence date |
||
| 1163 | mapi_setprops($this->message, Array($this->proptags["startdate"] => $utcfirstoccstartdatetime )); |
||
| 1164 | mapi_setprops($this->message, Array($this->proptags["duedate"] => $utcfirstoccenddatetime )); |
||
| 1165 | mapi_setprops($this->message, Array($this->proptags["commonstart"] => $utcfirstoccstartdatetime )); |
||
| 1166 | mapi_setprops($this->message, Array($this->proptags["commonend"] => $utcfirstoccenddatetime )); |
||
| 1167 | |||
| 1168 | // Set Outlook properties, if it is an appointment |
||
| 1169 | if (isset($this->messageprops[$this->proptags["message_class"]]) && $this->messageprops[$this->proptags["message_class"]] == "IPM.Appointment") { |
||
| 1170 | // update real begin and real end date |
||
| 1171 | mapi_setprops($this->message, Array($this->proptags["startdate_recurring"] => $utcstart)); |
||
| 1172 | mapi_setprops($this->message, Array($this->proptags["enddate_recurring"] => $utcend)); |
||
| 1173 | |||
| 1174 | // recurrencetype |
||
| 1175 | // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype |
||
| 1176 | mapi_setprops($this->message, Array($this->proptags["recurrencetype"] => ((int) $this->recur["type"]) - 0x9)); |
||
| 1177 | |||
| 1178 | // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting |
||
| 1179 | mapi_setprops($this->message, Array($this->proptags["side_effects"] => 369)); |
||
| 1180 | } else { |
||
| 1181 | mapi_setprops($this->message, Array($this->proptags["side_effects"] => 3441)); |
||
| 1182 | } |
||
| 1183 | |||
| 1184 | // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog |
||
| 1185 | // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset |
||
| 1186 | // to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to |
||
| 1187 | // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time) |
||
| 1188 | // with the reminder flag set. |
||
| 1189 | $reminderprops = mapi_getprops($this->message, array($this->proptags["reminder_minutes"], $this->proptags["flagdueby"]) ); |
||
| 1190 | if(isset($reminderprops[$this->proptags["reminder_minutes"]]) ) { |
||
| 1191 | $occ = false; |
||
| 1192 | $occurrences = $this->getItems(time(), 0x7ff00000, 3, true); |
||
| 1193 | |||
| 1194 | for($i = 0, $len = count($occurrences) ; $i < $len; $i++) { |
||
| 1195 | // This will actually also give us appointments that have already started, but not yet ended. Since we want the next |
||
| 1196 | // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum |
||
| 1197 | // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case: |
||
| 1198 | // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item |
||
| 1199 | // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped. |
||
| 1200 | |||
| 1201 | if(($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) { |
||
| 1202 | $occ = $occurrences[$i]; |
||
| 1203 | break; |
||
| 1204 | } |
||
| 1205 | } |
||
| 1206 | |||
| 1207 | if($occ) { |
||
| 1208 | if (isset($reminderprops[$this->proptags["flagdueby"]])) { |
||
| 1209 | mapi_setprops($this->message, Array($this->proptags["flagdueby"] => $reminderprops[$this->proptags["flagdueby"]])); |
||
| 1210 | } else { |
||
| 1211 | mapi_setprops($this->message, Array($this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60) )); |
||
| 1212 | } |
||
| 1213 | } else { |
||
| 1214 | // Last reminder passed, no reminders any more. |
||
| 1215 | mapi_setprops($this->message, Array($this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7ff00000)); |
||
| 1216 | } |
||
| 1217 | } |
||
| 1218 | |||
| 1219 | // Default data |
||
| 1220 | // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information) |
||
| 1221 | $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00); |
||
| 1222 | |||
| 1223 | if(isset($this->recur["startocc"]) && isset($this->recur["endocc"])) { |
||
| 1224 | // Set start and endtime in minutes |
||
| 1225 | $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]); |
||
| 1226 | } |
||
| 1227 | |||
| 1228 | // Detailed exception data |
||
| 1229 | |||
| 1230 | $changed_items = $this->recur["changed_occurrences"]; |
||
| 1231 | |||
| 1232 | $rdata .= pack("v", count($changed_items)); |
||
| 1233 | |||
| 1234 | foreach($changed_items as $changed_item) |
||
| 1235 | { |
||
| 1236 | // Set start and end time of exception |
||
| 1237 | $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); |
||
| 1238 | $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); |
||
| 1239 | $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); |
||
| 1240 | |||
| 1241 | //Bitmask |
||
| 1242 | $bitmask = 0; |
||
| 1243 | |||
| 1244 | // Check for changed strings |
||
| 1245 | if(isset($changed_item["subject"])) { |
||
| 1246 | $bitmask |= 1 << 0; |
||
| 1247 | } |
||
| 1248 | |||
| 1249 | if(isset($changed_item["remind_before"])) { |
||
| 1250 | $bitmask |= 1 << 2; |
||
| 1251 | } |
||
| 1252 | |||
| 1253 | if(isset($changed_item["reminder_set"])) { |
||
| 1254 | $bitmask |= 1 << 3; |
||
| 1255 | } |
||
| 1256 | |||
| 1257 | if(isset($changed_item["location"])) { |
||
| 1258 | $bitmask |= 1 << 4; |
||
| 1259 | } |
||
| 1260 | |||
| 1261 | if(isset($changed_item["busystatus"])) { |
||
| 1262 | $bitmask |= 1 << 5; |
||
| 1263 | } |
||
| 1264 | |||
| 1265 | if(isset($changed_item["alldayevent"])) { |
||
| 1266 | $bitmask |= 1 << 7; |
||
| 1267 | } |
||
| 1268 | |||
| 1269 | if(isset($changed_item["label"])) { |
||
| 1270 | $bitmask |= 1 << 8; |
||
| 1271 | } |
||
| 1272 | |||
| 1273 | $rdata .= pack("v", $bitmask); |
||
| 1274 | |||
| 1275 | // Set "subject" |
||
| 1276 | if(isset($changed_item["subject"])) { |
||
| 1277 | // convert utf-8 to non-unicode blob string (us-ascii?) |
||
| 1278 | $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]); |
||
| 1279 | $length = strlen($subject); |
||
| 1280 | $rdata .= pack("vv", $length + 1, $length); |
||
| 1281 | $rdata .= pack("a".$length, $subject); |
||
| 1282 | } |
||
| 1283 | |||
| 1284 | if(isset($changed_item["remind_before"])) { |
||
| 1285 | $rdata .= pack("V", $changed_item["remind_before"]); |
||
| 1286 | } |
||
| 1287 | |||
| 1288 | if(isset($changed_item["reminder_set"])) { |
||
| 1289 | $rdata .= pack("V", $changed_item["reminder_set"]); |
||
| 1290 | } |
||
| 1291 | |||
| 1292 | if(isset($changed_item["location"])) { |
||
| 1293 | $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]); |
||
| 1294 | $length = strlen($location); |
||
| 1295 | $rdata .= pack("vv", $length + 1, $length); |
||
| 1296 | $rdata .= pack("a".$length, $location); |
||
| 1297 | } |
||
| 1298 | |||
| 1299 | if(isset($changed_item["busystatus"])) { |
||
| 1300 | $rdata .= pack("V", $changed_item["busystatus"]); |
||
| 1301 | } |
||
| 1302 | |||
| 1303 | if(isset($changed_item["alldayevent"])) { |
||
| 1304 | $rdata .= pack("V", $changed_item["alldayevent"]); |
||
| 1305 | } |
||
| 1306 | |||
| 1307 | if(isset($changed_item["label"])) { |
||
| 1308 | $rdata .= pack("V", $changed_item["label"]); |
||
| 1309 | } |
||
| 1310 | } |
||
| 1311 | |||
| 1312 | $rdata .= pack("V", 0); |
||
| 1313 | |||
| 1314 | // write extended data |
||
| 1315 | foreach($changed_items as $changed_item) |
||
| 1316 | { |
||
| 1317 | $rdata .= pack("V", 0); |
||
| 1318 | if(isset($changed_item["subject"]) || isset($changed_item["location"])) { |
||
| 1319 | $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"])); |
||
| 1320 | $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"])); |
||
| 1321 | $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"])); |
||
| 1322 | } |
||
| 1323 | |||
| 1324 | if(isset($changed_item["subject"])) { |
||
| 1325 | $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]); |
||
| 1326 | $length = iconv_strlen($subject, "UCS-2LE"); |
||
| 1327 | $rdata .= pack("v", $length); |
||
| 1328 | $rdata .= pack("a".$length*2, $subject); |
||
| 1329 | } |
||
| 1330 | |||
| 1331 | if(isset($changed_item["location"])) { |
||
| 1332 | $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]); |
||
| 1333 | $length = iconv_strlen($location, "UCS-2LE"); |
||
| 1334 | $rdata .= pack("v", $length); |
||
| 1335 | $rdata .= pack("a".$length*2, $location); |
||
| 1336 | } |
||
| 1337 | |||
| 1338 | if(isset($changed_item["subject"]) || isset($changed_item["location"])) { |
||
| 1339 | $rdata .= pack("V", 0); |
||
| 1340 | } |
||
| 1341 | } |
||
| 1342 | |||
| 1343 | $rdata .= pack("V", 0); |
||
| 1344 | |||
| 1345 | // Set props |
||
| 1346 | mapi_setprops($this->message, Array($this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true)); |
||
| 1347 | if(isset($this->tz) && $this->tz){ |
||
| 1348 | $timezone = "GMT"; |
||
| 1349 | if ($this->tz["timezone"]!=0){ |
||
| 1350 | // Create user readable timezone information |
||
| 1351 | $timezone = sprintf("(GMT %s%02d:%02d)", (-$this->tz["timezone"]>0 ? "+" : "-"), |
||
| 1352 | abs($this->tz["timezone"]/60), |
||
| 1353 | abs($this->tz["timezone"]%60)); |
||
| 1354 | } |
||
| 1355 | mapi_setprops($this->message, Array($this->proptags["timezone_data"] => $this->getTimezoneData($this->tz), |
||
| 1356 | $this->proptags["timezone"] => $timezone)); |
||
| 1357 | } |
||
| 1358 | } |
||
| 1359 | |||
| 1360 | /** |
||
| 1361 | * Function which converts a recurrence date timestamp to an unix date timestamp. |
||
| 1362 | * @author Steve Hardy |
||
| 1363 | * @param Int $rdate the date which will be converted |
||
| 1364 | * @return Int the converted date |
||
| 1365 | */ |
||
| 1366 | function recurDataToUnixData($rdate) |
||
| 1367 | { |
||
| 1368 | return ($rdate - 194074560) * 60 ; |
||
| 1369 | } |
||
| 1370 | |||
| 1371 | /** |
||
| 1372 | * Function which converts an unix date timestamp to recurrence date timestamp. |
||
| 1373 | * @author Johnny Biemans |
||
| 1374 | * @param Date $date the date which will be converted |
||
| 1375 | * @return Int the converted date in minutes |
||
| 1376 | */ |
||
| 1377 | function unixDataToRecurData($date) |
||
| 1378 | { |
||
| 1379 | return ($date / 60) + 194074560; |
||
| 1380 | } |
||
| 1381 | |||
| 1382 | /** |
||
| 1383 | * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves |
||
| 1384 | * @author Steve Hardy |
||
| 1385 | */ |
||
| 1386 | function GetTZOffset($ts) |
||
| 1387 | { |
||
| 1388 | $Offset = date("O", $ts); |
||
| 1389 | |||
| 1390 | $Parity = $Offset < 0 ? -1 : 1; |
||
| 1391 | $Offset = $Parity * $Offset; |
||
| 1392 | $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100; |
||
| 1393 | |||
| 1394 | return $Parity * $Offset; |
||
| 1395 | } |
||
| 1396 | |||
| 1397 | /** |
||
| 1398 | * gmtime() doesn't exist in standard PHP, so we have to implement it ourselves |
||
| 1399 | * @author Steve Hardy |
||
| 1400 | * @param Date $time |
||
| 1401 | * @return Date GMT Time |
||
| 1402 | */ |
||
| 1403 | function gmtime($time) |
||
| 1404 | { |
||
| 1405 | $TZOffset = $this->GetTZOffset($time); |
||
| 1406 | |||
| 1407 | $t_time = $time - $TZOffset * 60; #Counter adjust for localtime() |
||
| 1408 | $t_arr = localtime($t_time, 1); |
||
| 1409 | |||
| 1410 | return $t_arr; |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 1411 | } |
||
| 1412 | |||
| 1413 | function isLeapYear($year) { |
||
| 1414 | return ( $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0) ); |
||
| 1415 | } |
||
| 1416 | |||
| 1417 | function getMonthInSeconds($year, $month) |
||
| 1418 | { |
||
| 1419 | if( in_array($month, array(1,3,5,7,8,10,12) ) ) { |
||
| 1420 | $day = 31; |
||
| 1421 | } else if( in_array($month, array(4,6,9,11) ) ) { |
||
| 1422 | $day = 30; |
||
| 1423 | } else { |
||
| 1424 | $day = 28; |
||
| 1425 | if( $this->isLeapYear($year) == 1 ) |
||
| 1426 | $day++; |
||
| 1427 | } |
||
| 1428 | return $day * 24 * 60 * 60; |
||
| 1429 | } |
||
| 1430 | |||
| 1431 | /** |
||
| 1432 | * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour |
||
| 1433 | * @param int $year |
||
| 1434 | * @param int $month |
||
| 1435 | * @param int $week |
||
| 1436 | * @param int $day |
||
| 1437 | * @param int $hour |
||
| 1438 | * @return returns the timestamp of the given date, timezone-independent |
||
| 1439 | */ |
||
| 1440 | function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour) |
||
| 1441 | { |
||
| 1442 | // get first day of month |
||
| 1443 | $date = gmmktime(0,0,0,$month,0,$year + 1900); |
||
| 1444 | |||
| 1445 | // get wday info |
||
| 1446 | $gmdate = $this->gmtime($date); |
||
| 1447 | |||
| 1448 | $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week |
||
| 1449 | |||
| 1450 | $date += $week * 7 * 24 * 60 * 60; // go to correct week nr |
||
| 1451 | $date += $day * 24 * 60 * 60; |
||
| 1452 | $date += $hour * 60 * 60; |
||
| 1453 | |||
| 1454 | $gmdate = $this->gmtime($date); |
||
| 1455 | |||
| 1456 | // if we are in the next month, then back up a week, because week '5' means |
||
| 1457 | // 'last week of month' |
||
| 1458 | |||
| 1459 | if($gmdate["tm_mon"]+1 != $month) |
||
| 1460 | $date -= 7 * 24 * 60 * 60; |
||
| 1461 | |||
| 1462 | return $date; |
||
| 1463 | } |
||
| 1464 | |||
| 1465 | /** |
||
| 1466 | * getTimezone gives the timezone offset (in minutes) of the given |
||
| 1467 | * local date/time according to the given TZ info |
||
| 1468 | */ |
||
| 1469 | function getTimezone($tz, $date) |
||
| 1470 | { |
||
| 1471 | // No timezone -> GMT (+0) |
||
| 1472 | if(!isset($tz["timezone"])) |
||
| 1473 | return 0; |
||
| 1474 | |||
| 1475 | $dst = false; |
||
| 1476 | $gmdate = $this->gmtime($date); |
||
| 1477 | |||
| 1478 | $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]); |
||
| 1479 | $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]); |
||
| 1480 | |||
| 1481 | if($dststart <= $dstend) { |
||
| 1482 | // Northern hemisphere, eg DST is during Mar-Oct |
||
| 1483 | if($date > $dststart && $date < $dstend) { |
||
| 1484 | $dst = true; |
||
| 1485 | } |
||
| 1486 | } else { |
||
| 1487 | // Southern hemisphere, eg DST is during Oct-Mar |
||
| 1488 | if($date < $dstend || $date > $dststart) { |
||
| 1489 | $dst = true; |
||
| 1490 | } |
||
| 1491 | } |
||
| 1492 | |||
| 1493 | if($dst) { |
||
| 1494 | return $tz["timezone"] + $tz["timezonedst"]; |
||
| 1495 | } else { |
||
| 1496 | return $tz["timezone"]; |
||
| 1497 | } |
||
| 1498 | } |
||
| 1499 | |||
| 1500 | /** |
||
| 1501 | * getWeekNr() returns the week nr of the month (ie first week of february is 1) |
||
| 1502 | */ |
||
| 1503 | function getWeekNr($date) |
||
| 1504 | { |
||
| 1505 | $gmdate = gmtime($date); |
||
| 1506 | $gmdate["tm_mday"] = 0; |
||
| 1507 | return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1; |
||
| 1508 | } |
||
| 1509 | |||
| 1510 | /** |
||
| 1511 | * parseTimezone parses the timezone as specified in named property 0x8233 |
||
| 1512 | * in Outlook calendar messages. Returns the timezone in minutes negative |
||
| 1513 | * offset (GMT +2:00 -> -120) |
||
| 1514 | */ |
||
| 1515 | function parseTimezone($data) |
||
| 1516 | { |
||
| 1517 | if(strlen($data) < 48) |
||
| 1518 | return; |
||
| 1519 | |||
| 1520 | $tz = unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data); |
||
| 1521 | return $tz; |
||
| 1522 | } |
||
| 1523 | |||
| 1524 | function getTimezoneData($tz) |
||
| 1525 | { |
||
| 1526 | $data = 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); |
||
| 1527 | |||
| 1528 | return $data; |
||
| 1529 | } |
||
| 1530 | |||
| 1531 | /** |
||
| 1532 | * createTimezone creates the timezone as specified in the named property 0x8233 |
||
| 1533 | * see also parseTimezone() |
||
| 1534 | * $tz is an array with the timezone data |
||
| 1535 | */ |
||
| 1536 | function createTimezone($tz) |
||
| 1537 | { |
||
| 1538 | $data = pack("lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx", |
||
| 1539 | $tz["timezone"], |
||
| 1540 | array_key_exists("timezonedst",$tz)?$tz["timezonedst"]:0, |
||
| 1541 | array_key_exists("dstendmonth",$tz)?$tz["dstendmonth"]:0, |
||
| 1542 | array_key_exists("dstendweek",$tz)?$tz["dstendweek"]:0, |
||
| 1543 | array_key_exists("dstendhour",$tz)?$tz["dstendhour"]:0, |
||
| 1544 | array_key_exists("dststartmonth",$tz)?$tz["dststartmonth"]:0, |
||
| 1545 | array_key_exists("dststartweek",$tz)?$tz["dststartweek"]:0, |
||
| 1546 | array_key_exists("dststarthour",$tz)?$tz["dststarthour"]:0 |
||
| 1547 | ); |
||
| 1548 | |||
| 1549 | return $data; |
||
| 1550 | } |
||
| 1551 | |||
| 1552 | /** |
||
| 1553 | * toGMT returns a timestamp in GMT time for the time and timezone given |
||
| 1554 | */ |
||
| 1555 | function toGMT($tz, $date) { |
||
| 1556 | if(!isset($tz['timezone'])) |
||
| 1557 | return $date; |
||
| 1558 | $offset = $this->getTimezone($tz, $date); |
||
| 1559 | |||
| 1560 | return $date + $offset * 60; |
||
| 1561 | } |
||
| 1562 | |||
| 1563 | /** |
||
| 1564 | * fromGMT returns a timestamp in the local timezone given from the GMT time given |
||
| 1565 | */ |
||
| 1566 | function fromGMT($tz, $date) { |
||
| 1567 | $offset = $this->getTimezone($tz, $date); |
||
| 1568 | |||
| 1569 | return $date - $offset * 60; |
||
| 1570 | } |
||
| 1571 | |||
| 1572 | /** |
||
| 1573 | * Function to get timestamp of the beginning of the day of the timestamp given |
||
| 1574 | * @param date $date |
||
| 1575 | * @return date timestamp referring to same day but at 00:00:00 |
||
| 1576 | */ |
||
| 1577 | function dayStartOf($date) |
||
| 1578 | { |
||
| 1579 | $time1 = $this->gmtime($date); |
||
| 1580 | |||
| 1581 | return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900); |
||
| 1582 | } |
||
| 1583 | |||
| 1584 | /** |
||
| 1585 | * Function to get timestamp of the beginning of the month of the timestamp given |
||
| 1586 | * @param date $date |
||
| 1587 | * @return date Timestamp referring to same month but on the first day, and at 00:00:00 |
||
| 1588 | */ |
||
| 1589 | function monthStartOf($date) |
||
| 1590 | { |
||
| 1591 | $time1 = $this->gmtime($date); |
||
| 1592 | |||
| 1593 | return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900); |
||
| 1594 | } |
||
| 1595 | |||
| 1596 | /** |
||
| 1597 | * Function to get timestamp of the beginning of the year of the timestamp given |
||
| 1598 | * @param date $date |
||
| 1599 | * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00 |
||
| 1600 | */ |
||
| 1601 | function yearStartOf($date) |
||
| 1602 | { |
||
| 1603 | $time1 = $this->gmtime($date); |
||
| 1604 | |||
| 1605 | return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900); |
||
| 1606 | } |
||
| 1607 | |||
| 1608 | |||
| 1609 | /** |
||
| 1610 | * Function which returns the items in a given interval. This included expansion of the recurrence and |
||
| 1611 | * processing of exceptions (modified and deleted). |
||
| 1612 | * |
||
| 1613 | * @param string $entryid the entryid of the message |
||
| 1614 | * @param array $props the properties of the message |
||
| 1615 | * @param date $start start time of the interval (GMT) |
||
| 1616 | * @param date $end end time of the interval (GMT) |
||
| 1617 | */ |
||
| 1618 | function getItems($start, $end, $limit = 0, $remindersonly = false) |
||
| 1619 | { |
||
| 1620 | $items = array(); |
||
| 1621 | |||
| 1622 | if(isset($this->recur)) { |
||
| 1623 | |||
| 1624 | // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which |
||
| 1625 | // exceptions are in range and have a reminder set |
||
| 1626 | if($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) { |
||
| 1627 | // Sort exceptions by start time |
||
| 1628 | uasort($this->recur["changed_occurrences"], array($this, "sortExceptionStart")); |
||
| 1629 | |||
| 1630 | // Loop through all changed exceptions |
||
| 1631 | foreach($this->recur["changed_occurrences"] as $exception) { |
||
| 1632 | // Check reminder set |
||
| 1633 | if(!isset($exception["reminder"]) || $exception["reminder"] == false) |
||
| 1634 | continue; |
||
| 1635 | |||
| 1636 | // Convert to GMT |
||
| 1637 | $occstart = $this->toGMT($tz, $exception["start"]); |
||
| 1638 | $occend = $this->toGMT($tz, $exception["end"]); |
||
| 1639 | |||
| 1640 | // Check range criterium |
||
| 1641 | if($occstart > $end || $occend < $start) |
||
| 1642 | continue; |
||
| 1643 | |||
| 1644 | // OK, add to items. |
||
| 1645 | array_push($items, $this->getExceptionProperties($exception)); |
||
| 1646 | if($limit && (count($items) == $limit)) |
||
| 1647 | break; |
||
| 1648 | } |
||
| 1649 | |||
| 1650 | uasort($items, array($this, "sortStarttime")); |
||
| 1651 | |||
| 1652 | return $items; |
||
| 1653 | } |
||
| 1654 | |||
| 1655 | // From here on, the dates of the occurrences are calculated in local time, so the days we're looking |
||
| 1656 | // at are calculated from the local time dates of $start and $end |
||
| 1657 | |||
| 1658 | if (isset($this->recur['regen']) && $this->recur['regen'] && isset($this->action['datecompleted'])) { |
||
| 1659 | $daystart = $this->dayStartOf($this->action['datecompleted']); |
||
| 1660 | } else { |
||
| 1661 | $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence |
||
| 1662 | } |
||
| 1663 | |||
| 1664 | // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view |
||
| 1665 | // or the end of the recurrence, whichever comes first |
||
| 1666 | if($end > $this->toGMT($this->tz, $this->recur["end"])) { |
||
| 1667 | $rangeend = $this->toGMT($this->tz, $this->recur["end"]); |
||
| 1668 | } else { |
||
| 1669 | $rangeend = $end; |
||
| 1670 | } |
||
| 1671 | |||
| 1672 | $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend)); |
||
| 1673 | |||
| 1674 | // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range. |
||
| 1675 | |||
| 1676 | switch($this->recur["type"]) |
||
| 1677 | { |
||
| 1678 | case 10: |
||
| 1679 | // Daily |
||
| 1680 | if($this->recur["everyn"] <= 0) |
||
| 1681 | $this->recur["everyn"] = 1440; |
||
| 1682 | |||
| 1683 | if($this->recur["subtype"] == 0) { |
||
| 1684 | // Every Nth day |
||
| 1685 | for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) { |
||
| 1686 | $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1687 | } |
||
| 1688 | } else { |
||
| 1689 | // Every workday |
||
| 1690 | for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440) |
||
| 1691 | { |
||
| 1692 | $nowtime = $this->gmtime($now); |
||
| 1693 | if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace |
||
| 1694 | $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1695 | } |
||
| 1696 | } |
||
| 1697 | } |
||
| 1698 | break; |
||
| 1699 | case 11: |
||
| 1700 | // Weekly |
||
| 1701 | if($this->recur["everyn"] <= 0) |
||
| 1702 | $this->recur["everyn"] = 1; |
||
| 1703 | |||
| 1704 | // If sliding flag is set then move to 'n' weeks |
||
| 1705 | if ($this->recur['regen']) $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]); |
||
| 1706 | |||
| 1707 | for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"])) |
||
| 1708 | { |
||
| 1709 | if ($this->recur['regen']) { |
||
| 1710 | $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1711 | } else { |
||
| 1712 | // Loop through the whole following week to the first occurrence of the week, add each day that is specified |
||
| 1713 | for($wday = 0; $wday < 7; $wday++) |
||
| 1714 | { |
||
| 1715 | $daynow = $now + $wday * 60 * 60 * 24; |
||
| 1716 | //checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item |
||
| 1717 | if ($daynow <= $dayend){ |
||
| 1718 | $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
||
| 1719 | if(($this->recur["weekdays"] &(1 << $nowtime["tm_wday"]))) { // Selected ? |
||
| 1720 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1721 | } |
||
| 1722 | } |
||
| 1723 | } |
||
| 1724 | } |
||
| 1725 | } |
||
| 1726 | break; |
||
| 1727 | case 12: |
||
| 1728 | // Monthly |
||
| 1729 | if($this->recur["everyn"] <= 0) |
||
| 1730 | $this->recur["everyn"] = 1; |
||
| 1731 | |||
| 1732 | // Loop through all months from start to end of occurrence, starting at beginning of first month |
||
| 1733 | for($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 ) |
||
| 1734 | { |
||
| 1735 | if(isset($this->recur["monthday"]) &&($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months |
||
| 1736 | $difference = 1; |
||
| 1737 | if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) { |
||
| 1738 | $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1; |
||
| 1739 | } |
||
| 1740 | $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60); |
||
| 1741 | //checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item |
||
| 1742 | if ($daynow <= $dayend){ |
||
| 1743 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1744 | } |
||
| 1745 | } |
||
| 1746 | else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] of every N months |
||
| 1747 | // Sanitize input |
||
| 1748 | if($this->recur["weekdays"] == 0) |
||
| 1749 | $this->recur["weekdays"] = 1; |
||
| 1750 | |||
| 1751 | // If nday is not set to the last day in the month |
||
| 1752 | if ($this->recur["nday"] < 5) { |
||
| 1753 | // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched |
||
| 1754 | $ndaycounter = 0; |
||
| 1755 | // Find matching weekday in this month |
||
| 1756 | for($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; $day++) |
||
| 1757 | { |
||
| 1758 | $daynow = $now + $day * 60 * 60 * 24; |
||
| 1759 | $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
||
| 1760 | |||
| 1761 | if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
||
| 1762 | $ndaycounter ++; |
||
| 1763 | } |
||
| 1764 | // check the selected pattern is same as asked Nth weekday,If so set the firstday |
||
| 1765 | if($this->recur["nday"] == $ndaycounter){ |
||
| 1766 | $firstday = $day; |
||
| 1767 | break; |
||
| 1768 | } |
||
| 1769 | } |
||
| 1770 | // $firstday is the day of the month on which the asked pattern of nth weekday matches |
||
| 1771 | $daynow = $now + $firstday * 60 * 60 * 24; |
||
| 1772 | } else { |
||
| 1773 | // Find last day in the month ($now is the firstday of the month) |
||
| 1774 | $NumDaysInMonth = $this->daysInMonth($now, 1); |
||
| 1775 | $daynow = $now + (($NumDaysInMonth-1) * 24*60*60); |
||
| 1776 | |||
| 1777 | $nowtime = $this->gmtime($daynow); |
||
| 1778 | while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))==0){ |
||
| 1779 | $daynow -= 86400; |
||
| 1780 | $nowtime = $this->gmtime($daynow); |
||
| 1781 | } |
||
| 1782 | } |
||
| 1783 | |||
| 1784 | /** |
||
| 1785 | * 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. |
||
| 1786 | */ |
||
| 1787 | if ($daynow <= $dayend && $daynow >= $daystart){ |
||
| 1788 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz , $remindersonly); |
||
| 1789 | } |
||
| 1790 | } else if ($this->recur['regen']) { |
||
| 1791 | $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60); |
||
| 1792 | $now = $daystart +($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60); |
||
| 1793 | |||
| 1794 | if ($now <= $dayend) { |
||
| 1795 | $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1796 | } |
||
| 1797 | } |
||
| 1798 | } |
||
| 1799 | break; |
||
| 1800 | case 13: |
||
| 1801 | // Yearly |
||
| 1802 | if($this->recur["everyn"] <= 0) |
||
| 1803 | $this->recur["everyn"] = 12; |
||
| 1804 | |||
| 1805 | for($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 ) |
||
| 1806 | { |
||
| 1807 | if(isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month |
||
| 1808 | // recur["month"] is in minutes since the beginning of the year |
||
| 1809 | $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11] |
||
| 1810 | $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31] |
||
| 1811 | $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month |
||
| 1812 | if($monthday > $this->daysInMonth($monthstart, 1)) |
||
| 1813 | $monthday = $this->daysInMonth($monthstart, 1); // Cap $monthday on month length (eg 28 feb instead of 29 feb) |
||
| 1814 | $daynow = $monthstart + ($monthday-1) * 24 * 60 * 60; |
||
| 1815 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1816 | } |
||
| 1817 | else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] in month X of every N years |
||
| 1818 | |||
| 1819 | // Go the correct month |
||
| 1820 | $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60; |
||
| 1821 | |||
| 1822 | // Find first matching weekday in this month |
||
| 1823 | for($wday = 0; $wday < 7; $wday++) |
||
| 1824 | { |
||
| 1825 | $daynow = $monthnow + $wday * 60 * 60 * 24; |
||
| 1826 | $nowtime = $this->gmtime($daynow); // Get the weekday of the current day |
||
| 1827 | |||
| 1828 | if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ? |
||
| 1829 | $firstday = $wday; |
||
| 1830 | break; |
||
| 1831 | } |
||
| 1832 | } |
||
| 1833 | |||
| 1834 | // Same as above (monthly) |
||
| 1835 | $daynow = $monthnow + ($firstday + ($this->recur["nday"]-1)*7) * 60 * 60 * 24; |
||
| 1836 | |||
| 1837 | while($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) { |
||
| 1838 | $daynow -= 7 * 60 * 60 * 24; |
||
| 1839 | } |
||
| 1840 | |||
| 1841 | $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1842 | } else if ($this->recur['regen']) { |
||
| 1843 | $year_starttime = $this->gmtime($now); |
||
| 1844 | $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1); // +1 next year |
||
| 1845 | $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /*year in seconds*/); |
||
| 1846 | |||
| 1847 | if ($now <= $dayend) { |
||
| 1848 | $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly); |
||
| 1849 | } |
||
| 1850 | } |
||
| 1851 | } |
||
| 1852 | } |
||
| 1853 | //to get all exception items |
||
| 1854 | if (!empty($this->recur['changed_occurrences'])) |
||
| 1855 | $this->processExceptionItems($items, $start, $end); |
||
| 1856 | } |
||
| 1857 | |||
| 1858 | // sort items on starttime |
||
| 1859 | usort($items, array($this, "sortStarttime")); |
||
| 1860 | |||
| 1861 | // Return the MAPI-compatible list of items for this object |
||
| 1862 | return $items; |
||
| 1863 | } |
||
| 1864 | |||
| 1865 | function sortStarttime($a, $b) |
||
| 1866 | { |
||
| 1867 | $aTime = $a[$this->proptags["startdate"]]; |
||
| 1868 | $bTime = $b[$this->proptags["startdate"]]; |
||
| 1869 | |||
| 1870 | return $aTime==$bTime?0:($aTime>$bTime?1:-1); |
||
| 1871 | } |
||
| 1872 | |||
| 1873 | /** |
||
| 1874 | * daysInMonth |
||
| 1875 | * |
||
| 1876 | * Returns the number of days in the upcoming number of months. If you specify 1 month as |
||
| 1877 | * $months it will give you the number of days in the month of $date. If you specify more it |
||
| 1878 | * will also count the days in the upcoming months and add that to the number of days. So |
||
| 1879 | * if you have a date in march and you specify $months as 2 it will return 61. |
||
| 1880 | * @param Integer $date Specified date as timestamp from which you want to know the number |
||
| 1881 | * of days in the month. |
||
| 1882 | * @param Integer $months Number of months you want to know the number of days in. |
||
| 1883 | * @returns Integer Number of days in the specified amount of months. |
||
| 1884 | */ |
||
| 1885 | function daysInMonth($date, $months) { |
||
| 1886 | $days = 0; |
||
| 1887 | |||
| 1888 | for($i=0;$i<$months;$i++) { |
||
| 1889 | $days += date("t", $date + $days * 24 * 60 * 60); |
||
| 1890 | } |
||
| 1891 | |||
| 1892 | return $days; |
||
| 1893 | } |
||
| 1894 | |||
| 1895 | // Converts MAPI-style 'minutes' into the month of the year [0..11] |
||
| 1896 | function monthOfYear($minutes) { |
||
| 1897 | $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 |
||
| 1898 | |||
| 1899 | $d += $minutes*60; |
||
| 1900 | |||
| 1901 | $dtime = $this->gmtime($d); |
||
| 1902 | |||
| 1903 | return $dtime["tm_mon"]; |
||
| 1904 | } |
||
| 1905 | |||
| 1906 | function sortExceptionStart($a, $b) |
||
| 1907 | { |
||
| 1908 | return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1 ); |
||
| 1909 | } |
||
| 1910 | } |
||
| 1911 | ?> |
||
|
0 ignored issues
–
show
It is not recommended to use PHP's closing tag
?> in files other than templates.
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore. A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever. Loading history...
|
|||
| 1912 |