1<?php 2 3/** 4 * SquirrelMail Shared Calendar Plugin 5 * Copyright (C) 2004-2005 Paul Lesneiwski <pdontthink@angrynerds.com> 6 * This program is licensed under GPL. See COPYING for details 7 * 8 */ 9 10 11/** 12 * Event class 13 * 14 */ 15class Event 16{ 17 18 // iCal standard attributes 19 // 20 var $id; 21 var $sequence; 22 var $type; 23 var $summary; 24 var $description; 25 var $comments; 26 var $status; 27 var $priority; 28 var $createdOn; 29 var $lastUpdatedOn; 30 var $thisObjectsTimestamp; // not a Property object 31 var $startDateTime; 32 var $endDateTime; 33 var $due; 34 var $duration; 35 var $recurrenceRule; 36 var $recurrenceDates; 37 var $recurrenceExclusionRule; 38 var $recurrenceExclusionDates; 39 var $percentComplete; 40 41 42 // non-standard/internal attributes 43 // 44 var $dom; 45 var $parentCalendars = array(); 46 var $owners = array(); 47 var $readable_users = array(); 48 var $writeable_users = array(); 49 var $unknownAttributes = array(); 50 var $createdBy; 51 var $lastUpdatedBy; 52 53 54 // other 55 // 56 var $startDateCache = array(); 57 var $endDateCache = array(); 58 var $cachedOccurrences = array(); 59 var $cachedOccurrencesThruDate; 60 var $cachedEndDateTime = ''; 61 62 63 64 /** 65 * Event constructor 66 * 67 * @param mixed $id The ID of this event 68 * May be specified as a string or a Property object. 69 * @param mixed $sequence The edit sequence ID for this event 70 * May be specified as an int or a Property object. 71 * @param mixed $dom The domain this event belongs under 72 * May be specified as a string or a Property object. 73 * @param mixed $type The type of this event, which should 74 * correspond to the event type constants 75 * defined in {@link constants.php} 76 * May be specified as a string or a Property object. 77 * @param mixed $summary The name of this event 78 * May be specified as a string or a Property object. 79 * @param mixed $description The description of this event 80 * May be specified as a string or a Property object. 81 * @param mixed $comments Administrative notes, etc 82 * May be specified as a string or a Property object. 83 * @param mixed $status This event/todo's current status, which should 84 * correspond to the event or todo status constants 85 * defined in {@link constants.php} 86 * May be specified as a string or a Property object. 87 * @param mixed $priority The priority of this event, which should 88 * correspond to the event priority constants 89 * defined in {@link constants.php} 90 * May be specified as an int or a Property object. 91 * @param mixed $startDateTime The date and time the event starts 92 * May be specified as a UTC-formatted timestamp 93 * (or just a date stamp (for all day events)) 94 * string or a Property object. 95 * @param mixed $endDateTime The date and time the event ends 96 * May be specified as a UTC-formatted timestamp 97 * (or just a date stamp (for all day events)) 98 * string or a Property object. 99 * @param mixed $due The date and time the todo/task comes due 100 * May be specified as a UTC-formatted timestamp 101 * string or a Property object. 102 * @param mixed $duration The length of this event/todo, given as a 103 * period/duration string per vCal/iCal spec. 104 * May be specified as a string or a Property object. 105 * @param mixed $recurrenceRule The ruleset that determines recurrence of 106 * this event. 107 * May be specified as a string or a Property object. 108 * @param mixed $recurrenceDates A list of date(s) that determine when this 109 * event should recur. 110 * May be specified as an array of UTC-formatted 111 * timestamps or a Property object. 112 * @param mixed $recurrenceExclusionRule The ruleset that determines recurrence 113 * exceptions for this event. 114 * May be specified as a string or a Property object. 115 * @param mixed $recurrenceExclusionDates A list of date(s) that determine 116 * when this event should not recur. 117 * May be specified as an array of UTC-formatted 118 * timestamps or a Property object. 119 * @param mixed $percentComplete The completion status for this item 120 * (for todo/tasks only) 121 * May be specified as an int or a Property object. 122 * @param mixed $parentCalendars An array of calendar IDs for calendars upon which 123 * this event falls 124 * May be specified an array or a Property object. 125 * @param mixed $createdBy The name of the user who created this event 126 * May be specified as a string or a Property object. 127 * @param mixed $createdOn The date/time this event was created (optional; 128 * defaults to today's date) 129 * May be specified as a UTC-formatted timestamp 130 * string or a Property object. 131 * @param mixed $lastUpdatedBy The name of the user who last updated this event 132 * May be specified as a string or a Property object. 133 * @param mixed $lastUpdatedOn The date/time this event was last updated 134 * May be specified as a UTC-formatted timestamp 135 * string or a Property object. 136 * @param mixed $owners The users who share ownership of this event 137 * May be specified an array or a Property object. 138 * @param mixed $readable_users The users who have read access to this event 139 * May be specified an array or a Property object. 140 * @param mixed $writeable_users The users who have write access to this event 141 * May be specified an array or a Property object. 142 * @param array $unknownAttributes Extra unknown attributes in an 143 * array keyed by attribute name, although 144 * the value MUST be the full iCal line 145 * describing the property, INCLUDING its 146 * name. These properties are often 147 * derived from custom attributes from an 148 * imported iCal file 149 * 150 */ 151 function Event($id='', $sequence=0, $dom='', $type='INVALID', 152 $summary='', $description='', 153 $comments='', $status='', 154 $priority=SM_CAL_EVENT_PRIORITY_NORMAL, 155 $startDateTime='', $endDateTime='', $due='', $duration='', 156 $recurrenceRule='', $recurrenceDates=array(), 157 $recurrenceExclusionRule='', $recurrenceExclusionDates=array(), 158 $percentComplete=0, 159 $parentCalendars=array(), $createdBy='', $createdOn='', 160 $lastUpdatedBy='', $lastUpdatedOn='', $owners=array(), 161 $readable_users=array(), $writeable_users=array(), 162 $unknownAttributes=array()) 163 { 164 165 $this->thisObjectsTimestamp = time(); 166 167 if (is_object($id) && strtolower(get_class($id)) == 'property') 168 $this->id = $id; 169 else 170 $this->id = new Property('UID', $id); 171 172 if (is_object($sequence) && strtolower(get_class($sequence)) == 'property') 173 $this->sequence = $sequence; 174 else 175 $this->sequence = new Property('SEQUENCE', $sequence); 176 177 if (is_object($dom) && strtolower(get_class($dom)) == 'property') 178 $this->dom = $dom; 179 else 180 $this->dom = new Property('X-SQ-EVTDOMAIN', 181 strtr($dom, '@|_-.:/ \\', '________')); 182 183 if (is_object($type) && strtolower(get_class($type)) == 'property') 184 $this->type = $type; 185 else 186 $this->type = new Property('NO-ICAL-TYPE', $type); 187 188 if (is_object($summary) && strtolower(get_class($summary)) == 'property') 189 $this->summary = $summary; 190 else 191 $this->summary = new Property('SUMMARY', $summary); 192 193 if (is_object($description) && strtolower(get_class($description)) == 'property') 194 $this->description = $description; 195 else 196 $this->description = new Property('DESCRIPTION', $description); 197 198 if (is_object($comments) && strtolower(get_class($comments)) == 'property') 199 $this->comments = $comments; 200 else 201 $this->comments = new Property('COMMENT', $comments); 202 203 if (is_object($status) && strtolower(get_class($status)) == 'property') 204 $this->status = $status; 205 else 206 $this->status = new Property('STATUS', $status); 207 208 if (is_object($priority) && strtolower(get_class($priority)) == 'property') 209 $this->priority = $priority; 210 else 211 $this->priority = new Property('PRIORITY', $priority); 212 213 if (is_object($startDateTime) && strtolower(get_class($startDateTime)) == 'property') 214 $this->startDateTime = $startDateTime; 215 else if (strpos($startDateTime, 'T') !== FALSE) 216 $this->startDateTime = new Property('DTSTART', $startDateTime, 217 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 218 else 219 $this->startDateTime = new Property('DTSTART', $startDateTime, 220 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATE); 221 222 if (is_object($endDateTime) && strtolower(get_class($endDateTime)) == 'property') 223 $this->endDateTime = $endDateTime; 224 else if (strpos($endDateTime, 'T') !== FALSE) 225 $this->endDateTime = new Property('DTEND', $endDateTime, 226 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 227 else 228 $this->endDateTime = new Property('DTEND', $endDateTime, 229 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATE); 230 231 if (is_object($due) && strtolower(get_class($due)) == 'property') 232 $this->due = $due; 233 else 234 $this->due = new Property('DUE', $due, 235 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 236 237 if (is_object($duration) && strtolower(get_class($duration)) == 'property') 238 $this->duration = $duration; 239 else 240 $this->duration = new Property('DURATION', $duration); 241 242 if (is_object($recurrenceRule) && strtolower(get_class($recurrenceRule)) == 'property') 243 $this->recurrenceRule = $recurrenceRule; 244 else 245 $this->recurrenceRule = Property::extractICalProperty('RRULE:' . $recurrenceRule, SM_CAL_ICAL_PROPERTY_TYPE_RRULE); 246 //new Property('RRULE', $recurrenceRule, 247 //$recurrenceRule, SM_CAL_ICAL_PROPERTY_TYPE_RRULE); 248 249 if (is_object($recurrenceDates) && strtolower(get_class($recurrenceDates)) == 'property') 250 $this->recurrenceDates = $recurrenceDates; 251 else 252 $this->recurrenceDates = new Property('RDATE', $recurrenceDates, 253 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 254 255 if (is_object($recurrenceExclusionRule) && strtolower(get_class($recurrenceExclusionRule)) == 'property') 256 $this->recurrenceExclusionRule = $recurrenceExclusionRule; 257 else 258 $this->recurrenceExclusionRule = Property::extractICalProperty('EXRULE:' . $recurrenceExclusionRule, SM_CAL_ICAL_PROPERTY_TYPE_RRULE); 259 260 if (is_object($recurrenceExclusionDates) && strtolower(get_class($recurrenceExclusionDates)) == 'property') 261 $this->recurrenceExclusionDates = $recurrenceExclusionDates; 262 else 263 $this->recurrenceExclusionDates = new Property('EXDATE', $recurrenceExclusionDates, 264 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 265 266 if (is_object($percentComplete) && strtolower(get_class($percentComplete)) == 'property') 267 $this->percentComplete = $percentComplete; 268 else 269 $this->percentComplete = new Property('PERCENT-COMPLETE', $percentComplete); 270 271 if (is_object($parentCalendars) && strtolower(get_class($parentCalendars)) == 'property') 272 $this->parentCalendars = $parentCalendars; 273 else 274 $this->parentCalendars = new Property('X-SQ-EVTPARENTCALENDARS', $parentCalendars); 275 276 if (is_object($createdBy) && strtolower(get_class($createdBy)) == 'property') 277 $this->createdBy = $createdBy; 278 else 279 $this->createdBy = new Property('X-SQ-EVTCREATOR', $createdBy); 280 281 if (is_object($createdOn) && strtolower(get_class($createdOn)) == 'property') 282 $this->createdOn = $createdOn; 283 else 284 $this->createdOn = new Property('CREATED', 285 (empty($createdOn) ? gmdate('Ymd\THis\Z') : $createdOn), 286 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 287 288 if (is_object($lastUpdatedBy) && strtolower(get_class($lastUpdatedBy)) == 'property') 289 $this->lastUpdatedBy = $lastUpdatedBy; 290 else 291 $this->lastUpdatedBy = new Property('X-SQ-EVTLASTUPDATOR', 292 (empty($lastUpdatedBy) ? $this->createdBy() : $lastUpdatedBy)); 293 294 if (is_object($lastUpdatedOn) && strtolower(get_class($lastUpdatedOn)) == 'property') 295 $this->lastUpdatedOn = $lastUpdatedOn; 296 else 297 $this->lastUpdatedOn = new Property('LAST-MODIFIED', 298 (empty($lastUpdatedOn) ? $this->createdOn->getRawValue() : $lastUpdatedOn), 299 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC); 300 301 if (is_object($owners) && strtolower(get_class($owners)) == 'property') 302 $this->owners = $owners; 303 else 304 $this->owners = new Property('X-SQ-EVTOWNERS', $owners); 305 306 if (is_object($readable_users) && strtolower(get_class($readable_users)) == 'property') 307 $this->readable_users = $readable_users; 308 else 309 $this->readable_users = new Property('X-SQ-EVTREADABLEUSERS', $readable_users); 310 311 if (is_object($writeable_users) && strtolower(get_class($writeable_users)) == 'property') 312 $this->writeable_users = $writeable_users; 313 else 314 $this->writeable_users = new Property('X-SQ-EVTWRITEABLEUSERS', $writeable_users); 315 316 $this->unknownAttributes = $unknownAttributes; 317 318 319 320 global $domain, $username, $useDomainInCalID; 321 322 323 // insert default values if not given above 324 // 325 // note that user is only given read access by default; 326 // caller must explicitly give any more than that 327 // 328 $o = $this->getOwners(); 329 $w = $this->getWriteableUsers(); 330 $r = $this->getReadableUsers(); 331 if (empty($o) && empty($w) && empty($r)) 332 $this->readable_users->setValue(array($username)); 333 334 $c = $this->createdBy(); 335 if (empty($c)) 336 $this->setCreator($username); 337 338 339 // um, we actually want events to have the same permissions 340 // that their parent calendars have... per-event permissioning 341 // is not currently useful; this is the least costly place 342 // to duplicate parent calendar permissions, short of removing 343 // the checks in the code for event permissions 344 // 345 $this->resetPermissionsToParent(); 346 347 348 $d = $this->dom->getValue(); 349 if (empty($d)) 350 $this->dom->setValue(strtr($domain, '@|_-.:/ \\', '________')); 351 352 $i = $this->getID(); 353 if (empty($i)) 354 $this->id->setValue('sm_cal_evt_' . gmdate('Ymd\THis\Z') 355 . ($useDomainInCalID ? '_' . $this->getDomain() : '')); 356 357 358 359 // make sure RDATE and EXDATE values are always ordered arrays 360 // 361 $rdates = $this->getRecurrenceDates(); 362 if (empty($rdates)) $this->recurrenceDates->resetValue(array()); 363 else if (!is_array($rdates)) $this->recurrenceDates->resetValue(array($rdates)); 364 else 365 { 366 367 // is this a PERIOD or regular date/time? 368 // 369 if ($this->recurrenceDates->getType() == SM_CAL_ICAL_PROPERTY_TYPE_PERIOD) 370 { 371 372 // values are always arrays (start/end of period), 373 // but multiple periods will be stored as arrays of 374 // arrays (start/end, start/end, start/end, etc) 375 // 376 // we only need to resort if there is more than 377 // one period, which we can determine by seeing 378 // if we have nested values here (if not, make 379 // sure we make it nested anyway) 380 // 381 if (!is_array($rdates[0])) 382 $this->recurrenceDates->resetValue(array($rdates)); 383 384 // sort nested arrays based on the period's starting 385 // (and secondly ending) timestamp(s) 386 // 387 else 388 { 389 usort($rdates, 'sortPeriods'); // see functions.php for function sortPeriods() 390 $this->recurrenceDates->resetValue($rdates); 391 } 392 393 } 394 395 396 // regular date or date/time - just make sure they are sorted 397 // 398 else 399 { 400 sort($rdates); 401 $this->recurrenceDates->resetValue($rdates); 402 } 403 404 } 405 406 $exdates = $this->getRecurrenceExclusionDates(); 407 if (empty($exdates)) $this->recurrenceExclusionDates->resetValue(array()); 408 else if (!is_array($exdates)) $this->recurrenceExclusionDates->resetValue(array($exdates)); 409 else 410 { 411 sort($exdates); 412 $this->recurrenceExclusionDates->resetValue($exdates); 413 } 414 415 416 417 return $this->recurrenceDates->getValue(); 418 419 420 } 421 422 423 424// ---------- PRIVATE ---------- 425 426 427 428 /** 429 * Find the next possible event occurrence for a given interval 430 * in the recurrence rule. 431 * 432 * Also considers if there are any recurrence dates that come 433 * first. 434 * 435 * @param array $recurrenceRule The rule to follow for the given 436 * recurrence interval 437 * @param array $recurrenceDates The set of recurrence dates 438 * for the event (passed by ref) 439 * @param int $currentOccurrenceDate Timestamp for the current 440 * event iteration, after which 441 * to start looking for the next 442 * occurrence (optional, if given 443 * as empty string, the first 444 * possible occurrence is returned) 445 * @param int $currentIntervalBasis The timestamp of the base date 446 * that defines this interval 447 * @param int $maxDate The timestamp indicating the date past which 448 * we needn't keep iterating (optional; default 449 * is the constant MAX_RECURRENCE_DATE, as 450 * defined in {@link constants.php}) 451 * NOTE that to avoid time-of-day problems, 452 * we typically iterate one day past this date 453 * 454 * @return mixed If a regular occurrence was found following the 455 * given recurrence rule, that timestamp is returned; 456 * If a date was instead found from amongst the given 457 * recurrence dates, that timestamp is returned inside 458 * a one-element array; If no occurrences are found 459 * in this particular interval, 0 (zero) is returned; 460 * If we exceed either $maxDate or the UNTIL clause 461 * of the given recurrence rule, -1 is returned. 462 * 463 * @access private 464 * 465 */ 466 function inspectIntraIntervalOccurrences($recurrenceRule, &$recurrenceDates, 467 $currentOccurrenceDate='', 468 $currentIntervalBasis, 469 $maxDate=MAX_RECURRENCE_DATE) 470 { 471 472 global $WEEKDAYS, $week_start_day; 473 474 475 // no recurrence rule? just bail 476 // 477 if (empty($recurrenceRule)) return 0; 478 479 480 // add a day to maxDate 481 // 482 $maxDate += SM_CAL_DAY_SECONDS; 483 484 485// just shorthand notes... not technically all correct... see RFC for that... 486// BYSECOND=[0-59]{1,*} 487// BYMINUTE=[0-59]{1,*} 488// BYHOUR=[0-23]{1,*} 489// BYDAY=[[+/-][1-53]][MO/TU/WE/TH/FR/SA/SU] 490// leading number only allowable when freq is monthly or yearly 491// BYMONTHDAY=[+/-][1.31]{1,*} 492// BYYEARDAY=[+/-][1-366]{1,*} 493// BYWEEKNO=[+/-][1-53]{1,*} 494// only valid with YEARLY freq 495// BYMONTH=[1-12]{1,*} 496// BYSETPOS=[+/-][1-366]{1,*} 497// if given, one other BY**** MUST be given as well (well, ok, we can just ignore it if not) 498// WKST=[MO/TU/WE/TH/FR/SA/SU]{1} 499// default = MO, only applicable to ((FREQ == WEEKLY && INTERVAL > 1 && !empty(BYDAY)) 500// || (FREQ == YEARLY && !empty(BYWEEKNO))) 501 502 503 list($year, $month, $day, $hour, $minute, $second, $dayOfWeek, 504 $weekNumber, $numberOfDaysInMonth) 505 = explode('-', date('Y-n-j-G-i-s-w-W-t', $currentIntervalBasis)); 506 507 // can be 53 and 366 some years (but note that z is zero-based) 508 // 509 list($numberOfWeeksInYear, $numberOfDaysInYear) 510 = explode('-', date('W-z', mktime(12, 0, 0, 12, 31, $year))); 511 $numberOfDaysInYear++; 512 513 if (!isset($recurrenceRule['WKST'])) // don't check if empty, since zero is valid 514 $weekStart = $week_start_day; 515 else 516 $weekStart = $recurrenceRule['WKST']; 517 $weekEnd = $weekStart - 1; 518 if ($weekEnd == -1) $weekEnd = 6; 519 $occurrences = array(); 520 521 522 // build an array of occurrences within the given interval 523 // 524 switch ($recurrenceRule['FREQ']) 525 { 526 527 //--------------------------------------------- 528 // 529 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_YEARLY: 530 531 // BYMONTHLY 532 // 533 foreach ($recurrenceRule['BYMONTH'] as $MON) 534 $occurrences[] = mktime($hour, $minute, $second, $MON, $day, $year); 535 536 537 538 // BYWEEKNO 539 // 540 // Use date of "WKST" for each week as our occurrence set 541 // 542 $weeknoOccurrences = array(); 543 foreach ($recurrenceRule['BYWEEKNO'] as $WEEKNO) 544 { 545 if ($WEEKNO < 0) 546 $WEEKNO = $numberOfWeeksInYear + $WEEKNO + 1; 547 $weeknoOccurrences[] = datefromweeknr($year, $WEEKNO, $weekStart); 548 } 549 $temp_occurrences = array(); 550 if (empty($occurrences)) $occurrences = $weeknoOccurrences; 551 552 // unless BYMONTHLY was given, in which case we limit 553 // our set to those BYWEEKNO occurrences that fall in the 554 // months given by BYMONTHLY 555 // 556 else if (!empty($weeknoOccurrences)) 557 { 558 foreach ($weeknoOccurrences as $timestamp) 559 { 560 foreach ($occurrences as $occurStamp) 561 { 562 if (date('n', $timestamp) == date('n', $occurStamp)) 563 { 564 $temp_occurrences[] = $timestamp; 565 break; 566 } 567 } 568 } 569 $occurrences = $temp_occurrences; 570 } 571 572 573 574 // BYYEARDAY 575 // 576 // Straight-forward... 577 // 578 $yearDayOccurrences = array(); 579 foreach ($recurrenceRule['BYYEARDAY'] as $YEARDAY) 580 { 581 if ($YEARDAY < 0) 582 $YEARDAY = $numberOfDaysInYear + $YEARDAY + 1; 583 $yearDayOccurrences[] = mktime($hour, $minute, $second, 1, $YEARDAY, $year); 584 } 585 $temp_occurrences = array(); 586 if (empty($occurrences)) $occurrences = $yearDayOccurrences; 587 588 // unless one of the above was given too... 589 // 590 else if (!empty($yearDayOccurrences)) 591 { 592 foreach ($yearDayOccurrences as $timestamp) 593 { 594 foreach ($occurrences as $occurStamp) 595 { 596 597 // when only BYMONTH was given, limit occurrence set 598 // to those year days that fall in the given months 599 // 600 if ((!empty($recurrenceRule['BYMONTH']) && empty($recurrenceRule['BYWEEKNO']) 601 && date('n', $timestamp) == date('n', $occurStamp)) 602 603 // when only BYWEEKNO was given, limit occurrence set 604 // to the year days that come in the BYWEEKNO weeks 605 // 606 || (empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 607 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS) 608 609 // when both BYMONTHLY *and* BYWEEKNO are given, limit occurrence set 610 // to the year days that come in the BYWEEKNO weeks only in the 611 // BYMONTHLY months. whew. 612 // 613 || (!empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 614 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS 615 && date('n', $timestamp) == date('n', $occurStamp))) 616 { 617 $temp_occurrences[] = $timestamp; 618 break; 619 } 620 } 621 } 622 $occurrences = $temp_occurrences; 623 } 624 625 626 627 // BYMONTHDAY 628 // 629 // Straight-forward... 630 // 631 $monthDayOccurrences = array(); 632 if (!empty($recurrenceRule['BYMONTHDAY'])) for ($m = 1; $m < 13; $m++) 633 { 634 $numberOfDaysInMo = date('t', mktime(12, 0, 0, $m, 15, $year)); 635 foreach ($recurrenceRule['BYMONTHDAY'] as $DAY) 636 { 637 if ($DAY < 0) 638 $DAY = $numberOfDaysInMo + $DAY + 1; 639 $monthDayOccurrences[] = mktime($hour, $minute, $second, $m, $DAY, $year); 640 } 641 } 642 $temp_occurrences = array(); 643 if (empty($occurrences)) $occurrences = $monthDayOccurrences; 644 645 // unless one of the above was given too... 646 // 647 else if (!empty($monthDayOccurrences)) 648 { 649 foreach ($monthDayOccurrences as $timestamp) 650 { 651 foreach ($occurrences as $occurStamp) 652 { 653 654 // when only BYMONTH was given, limit occurrence set 655 // to those month days that fall in the given months 656 // 657 if ((!empty($recurrenceRule['BYMONTH']) && empty($recurrenceRule['BYWEEKNO']) 658 && empty($recurrenceRule['BYYEARDAY']) 659 && date('n', $timestamp) == date('n', $occurStamp)) 660 661 // when only BYWEEKNO was given, limit occurrence set 662 // to the month days that come in the BYWEEKNO weeks 663 // 664 || (empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 665 && empty($recurrenceRule['BYYEARDAY']) 666 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS) 667 668 // when both BYMONTHLY *and* BYWEEKNO are given, limit occurrence set 669 // to the month days that come in the BYWEEKNO weeks only in the 670 // BYMONTHLY months. whew. 671 // 672 || (!empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 673 && empty($recurrenceRule['BYYEARDAY']) 674 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS 675 && date('n', $timestamp) == date('n', $occurStamp)) 676 677 // when BYYEARDAY was given - no matter what 678 // other fields are given, limit occurrence set 679 // to the month days that intersect those year 680 // days already selected 681 // 682 || (!empty($recurrenceRule['BYYEARDAY']) 683 && $timestamp == $occurStamp)) 684 { 685 $temp_occurrences[] = $timestamp; 686 break; 687 } 688 } 689 } 690 $occurrences = $temp_occurrences; 691 } 692 693 694 695 // BYDAY 696 // 697 // pick all days in the year that are in BYDAY, 698 // optionally limited by the count given in front of BYDAY values 699 // 700 $dayOccurrences = array(); 701 $dayOfYearCount = array(0 => array(), 1 => array(), 2 => array(), 702 3 => array(), 4 => array(), 5 => array(), 703 6 => array()); 704 $byDayRule = array(); 705 foreach ($recurrenceRule['BYDAY'] as $DAY) 706 { 707 if (!array_key_exists(strtoupper($DAY), $WEEKDAYS)) 708 { 709 $dayCount = substr($DAY, 0, strlen($DAY) - 2); 710 $dy = $WEEKDAYS[substr($DAY, strlen($DAY) - 2)]; 711 } 712 else 713 { 714 $dayCount = 0; 715 $dy = $WEEKDAYS[$DAY]; 716 } 717//LEFT OFF HERE: make sure zero array indexes don't screw something up 718 $byDayRule[$dy] = $dayCount; 719 } 720 if (!empty($recurrenceRule['BYDAY'])) for ($d = 1; $d <= $numberOfDaysInYear; $d++) 721 { 722 $timestamp = mktime($hour, $minute, $second, 1, $d, $year); 723 $dyOfYr = date('w', $timestamp); 724 $dayOfYearCount[$dyOfYr][] = $timestamp; 725 foreach ($byDayRule as $DAY => $dayCount) 726 { 727 if ($DAY == $dyOfYr 728 && ($dayCount === 0 || $dayCount == sizeof($dayOfYearCount[$DAY]))) 729 $dayOccurrences[] = $timestamp; 730 } 731 } 732 // get negative offsets 733 // 734 foreach ($byDayRule as $DAY => $dayCount) 735 { 736 if ($dayCount < 0) 737 { 738 $dayOccurrences[] 739 = $dayOfYearCount[$DAY][sizeof($dayOfYearCount[$DAY]) + $dayCount]; 740 } 741 } 742 $temp_occurrences = array(); 743 if (empty($occurrences)) $occurrences = $dayOccurrences; 744 745 // unless one of the above was given too... 746 // 747 else if (!empty($dayOccurrences)) 748 { 749 foreach ($dayOccurrences as $timestamp) 750 { 751 foreach ($occurrences as $occurStamp) 752 { 753 754 // when only BYMONTH was given, limit occurrence set 755 // to those days that fall in the given months 756 // 757 if ((!empty($recurrenceRule['BYMONTH']) && empty($recurrenceRule['BYWEEKNO']) 758 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 759 && date('n', $timestamp) == date('n', $occurStamp)) 760 761 // when only BYWEEKNO was given, limit occurrence set 762 // to the days that come in the BYWEEKNO weeks 763 // 764 || (empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 765 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 766 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS) 767 768 // when both BYMONTHLY *and* BYWEEKNO are given, limit occurrence set 769 // to the days that come in the BYWEEKNO weeks only in the 770 // BYMONTHLY months. whew. 771 // 772 || (!empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 773 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 774 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS 775 && date('n', $timestamp) == date('n', $occurStamp)) 776 777 // when either BYYEARDAY or BYMONTHDAY was 778 // given - no matter what other fields are 779 // given, limit occurrence set to the 780 // days that intersect those year days 781 // and month days already selected 782 // 783 || ((!empty($recurrenceRule['BYYEARDAY']) 784 || !empty($recurrenceRule['BYMONTHDAY'])) 785 && $timestamp == $occurStamp)) 786 { 787 $temp_occurrences[] = $timestamp; 788 break; 789 } 790 } 791 } 792 $occurrences = $temp_occurrences; 793 } 794 795 796 797 // BYHOUR 798 // 799 // Straight-forward... 800 // 801 $hourlyOccurrences = array(); 802 if (!empty($recurrenceRule['BYHOUR'])) for ($d = 1; $d <= $numberOfDaysInYear; $d++) 803 { 804 foreach ($recurrenceRule['BYHOUR'] as $h) 805 $hourlyOccurrences[] = mktime($h, $minute, $second, 1, $d, $year); 806 } 807 $temp_occurrences = array(); 808 if (empty($occurrences)) $occurrences = $hourlyOccurrences; 809 810 // unless one of the above was given too... 811 // 812 else if (!empty($hourlyOccurrences)) 813 { 814 foreach ($hourlyOccurrences as $timestamp) 815 { 816 foreach ($occurrences as $occurStamp) 817 { 818 819 // when only BYMONTH was given, limit occurrence set 820 // to those hours that fall in the given months 821 // 822 if ((!empty($recurrenceRule['BYMONTH']) && empty($recurrenceRule['BYWEEKNO']) 823 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 824 && empty($recurrenceRule['BYDAY']) 825 && date('n', $timestamp) == date('n', $occurStamp)) 826 827 // when only BYWEEKNO was given, limit occurrence set 828 // to the hours that come in the BYWEEKNO weeks 829 // 830 || (empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 831 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 832 && empty($recurrenceRule['BYDAY']) 833 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS) 834 835 // when both BYMONTHLY *and* BYWEEKNO are given, limit occurrence set 836 // to the hours that come in the BYWEEKNO weeks only in the 837 // BYMONTHLY months. whew. 838 // 839 || (!empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 840 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 841 && empty($recurrenceRule['BYDAY']) 842 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS 843 && date('n', $timestamp) == date('n', $occurStamp)) 844 845 // when BYYEARDAY or BYMONTHDAY or BYDAY were 846 // given - no matter what other fields are 847 // given, limit occurrence set to the 848 // hours that fall in those year and month days 849 // already selected 850 // 851 || ((!empty($recurrenceRule['BYYEARDAY']) 852 || !empty($recurrenceRule['BYMONTHDAY']) 853 || !empty($recurrenceRule['BYDAY'])) 854 && date('d', $timestamp) == date('d', $occurStamp))) 855 { 856 $temp_occurrences[] = $timestamp; 857 break; 858 } 859 } 860 } 861 $occurrences = $temp_occurrences; 862 } 863 864 865 866 // BYMINUTE 867 // 868 // Straight-forward, if inefficient... 869 // 870 $minuteOccurrences = array(); 871 if (!empty($recurrenceRule['BYMINUTE'])) for ($d = 1; $d <= $numberOfDaysInYear; $d++) 872 { 873 for ($h = 0; $h < 24; $h++) 874 foreach ($recurrenceRule['BYMINUTE'] as $mi) 875 $minuteOccurrences[] = mktime($h, $mi, $second, 1, $d, $year); 876 } 877 $temp_occurrences = array(); 878 if (empty($occurrences)) $occurrences = $minuteOccurrences; 879 880 // unless one of the above was given too... 881 // 882 else if (!empty($minuteOccurrences)) 883 { 884 foreach ($minuteOccurrences as $timestamp) 885 { 886 foreach ($occurrences as $occurStamp) 887 { 888 889 // when only BYMONTH was given, limit occurrence set 890 // to those minutes that fall in the given months 891 // 892 if ((!empty($recurrenceRule['BYMONTH']) && empty($recurrenceRule['BYWEEKNO']) 893 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 894 && empty($recurrenceRule['BYDAY']) && empty($recurrenceRule['BYHOUR']) 895 && date('n', $timestamp) == date('n', $occurStamp)) 896 897 // when only BYWEEKNO was given, limit occurrence set 898 // to the minutes that come in the BYWEEKNO weeks 899 // 900 || (empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 901 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 902 && empty($recurrenceRule['BYDAY']) && empty($recurrenceRule['BYHOUR']) 903 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS) 904 905 // when both BYMONTHLY *and* BYWEEKNO are given, limit occurrence set 906 // to the minutes that come in the BYWEEKNO weeks only in the 907 // BYMONTHLY months. whew. 908 // 909 || (!empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 910 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 911 && empty($recurrenceRule['BYDAY']) && empty($recurrenceRule['BYHOUR']) 912 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS 913 && date('n', $timestamp) == date('n', $occurStamp)) 914 915 // when BYYEARDAY or BYMONTHDAY or BYDAY 916 // were given - no matter what other 917 // fields are given (except BYHOUR), limit 918 // occurrence set to 919 // the minutes that fall in those year and 920 // month days already selected 921 // 922 || ((!empty($recurrenceRule['BYYEARDAY']) 923 || !empty($recurrenceRule['BYMONTHDAY']) 924 || !empty($recurrenceRule['BYDAY'])) 925 && empty($recurrenceRule['BYHOUR']) 926 && date('d', $timestamp) == date('d', $occurStamp)) 927 928 // when BYHOUR was given, no matter what other 929 // fields are given, limit occurrence set to the 930 // minutes that fall in those hours already selected 931 // 932 || (!empty($recurrenceRule['BYHOUR']) 933 && date('H', $timestamp) == date('H', $occurStamp))) 934 { 935 $temp_occurrences[] = $timestamp; 936 break; 937 } 938 } 939 } 940 $occurrences = $temp_occurrences; 941 } 942 943 944 945 // BYSECOND 946 // 947 // Straight-forward... (but really inefficient) 948 // 949 $secondOccurrences = array(); 950 if (!empty($recurrenceRule['BYSECOND'])) for ($d = 1; $d <= $numberOfDaysInYear; $d++) 951 { 952 for ($h = 0; $h < 24; $h++) 953 for ($mi = 0; $mi < 60; $mi++) 954 foreach ($recurrenceRule['BYSECOND'] as $s) 955 $secondOccurrences[] = mktime($h, $mi, $s, 1, $d, $year); 956 } 957 $temp_occurrences = array(); 958 if (empty($occurrences)) $occurrences = $secondOccurrences; 959 960 // unless one of the above was given too... 961 // 962 else if (!empty($secondOccurrences)) 963 { 964 foreach ($secondOccurrences as $timestamp) 965 { 966 foreach ($occurrences as $occurStamp) 967 { 968 969 // when only BYMONTH was given, limit occurrence set 970 // to those seconds that fall in the given months 971 // 972 if ((!empty($recurrenceRule['BYMONTH']) && empty($recurrenceRule['BYWEEKNO']) 973 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 974 && empty($recurrenceRule['BYDAY']) && empty($recurrenceRule['BYHOUR']) 975 && empty($recurrenceRule['BYMINUTE']) 976 && date('n', $timestamp) == date('n', $occurStamp)) 977 978 // when only BYWEEKNO was given, limit occurrence set 979 // to the seconds that come in the BYWEEKNO weeks 980 // 981 || (empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 982 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 983 && empty($recurrenceRule['BYDAY']) && empty($recurrenceRule['BYHOUR']) 984 && empty($recurrenceRule['BYMINUTE']) 985 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS) 986 987 // when both BYMONTHLY *and* BYWEEKNO are given, limit occurrence set 988 // to the seconds that come in the BYWEEKNO weeks only in the 989 // BYMONTHLY months. whew. 990 // 991 || (!empty($recurrenceRule['BYMONTH']) && !empty($recurrenceRule['BYWEEKNO']) 992 && empty($recurrenceRule['BYYEARDAY']) && empty($recurrenceRule['BYMONTHDAY']) 993 && empty($recurrenceRule['BYDAY']) && empty($recurrenceRule['BYHOUR']) 994 && empty($recurrenceRule['BYMINUTE']) 995 && $timestamp >= $occurStamp && $timestamp < $occurStamp + SM_CAL_WEEK_SECONDS 996 && date('n', $timestamp) == date('n', $occurStamp)) 997 998 // when BYYEARDAY or BYMONTHDAY or BYDAY 999 // were given - no matter what other fields 1000 // beside BYHOUR and BYMINUTE are given, limit occurrence 1001 // set to the seconds that fall in those year and 1002 // month days already selected 1003 // 1004 || ((!empty($recurrenceRule['BYYEARDAY']) 1005 || !empty($recurrenceRule['BYMONTHDAY']) 1006 || !empty($recurrenceRule['BYDAY'])) 1007 && empty($recurrenceRule['BYHOUR']) 1008 && empty($recurrenceRule['BYMINUTE']) 1009 && date('d', $timestamp) == date('d', $occurStamp)) 1010 1011 // when BYHOUR was given, no matter what other 1012 // fields are given beside BYMINUTE, limit occurrence 1013 // set to the seconds that fall in those hours already 1014 // selected 1015 // 1016 || (!empty($recurrenceRule['BYHOUR']) 1017 && empty($recurrenceRule['BYMINUTE']) 1018 && date('H', $timestamp) == date('H', $occurStamp)) 1019 1020 // when BYMINUTE was given, no matter what other 1021 // fields are given, limit occurrence 1022 // set to the seconds that fall in those minutes already 1023 // selected 1024 // 1025 || (!empty($recurrenceRule['BYMINUTE']) 1026 && date('i', $timestamp) == date('i', $occurStamp))) 1027 { 1028 $temp_occurrences[] = $timestamp; 1029 break; 1030 } 1031 } 1032 } 1033 $occurrences = $temp_occurrences; 1034 } 1035 1036 1037 1038 break; 1039 1040 1041 1042 //--------------------------------------------- 1043 // 1044 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MONTHLY: 1045 1046 // BYMONTHDAY 1047 // 1048 // Straight-forward... 1049 // 1050 foreach ($recurrenceRule['BYMONTHDAY'] as $DAY) 1051 { 1052 if ($DAY < 0) 1053 $DAY = $numberOfDaysInMonth + $DAY + 1; 1054 $occurrences[] = mktime($hour, $minute, $second, $month, $DAY, $year); 1055 } 1056 1057 1058 1059 // BYDAY 1060 // 1061 // pick all days in the month that are in BYDAY, 1062 // optionally limited by the count given in front of BYDAY values 1063 // 1064 $dayOccurrences = array(); 1065 $dayOfMonthCount = array(0 => array(), 1 => array(), 2 => array(), 1066 3 => array(), 4 => array(), 5 => array(), 1067 6 => array()); 1068 $byDayRule = array(); 1069 // separate day count offset and actual day name 1070 // 1071 foreach ($recurrenceRule['BYDAY'] as $DAY) 1072 { 1073 if (!array_key_exists(strtoupper($DAY), $WEEKDAYS)) 1074 { 1075 $dayCount = substr($DAY, 0, strlen($DAY) - 2); 1076 $dy = $WEEKDAYS[substr($DAY, strlen($DAY) - 2)]; 1077 } 1078 else 1079 { 1080 $dayCount = 0; 1081 $dy = $WEEKDAYS[$DAY]; 1082 } 1083//LEFT OFF HERE: make sure zero array indexes don't screw something up 1084 $byDayRule[$dy] = $dayCount; 1085 } 1086 // now deal with BYDAY rule itself 1087 // 1088 if (!empty($recurrenceRule['BYDAY'])) for ($d = 1; $d <= $numberOfDaysInMonth; $d++) 1089 { 1090 $timestamp = mktime($hour, $minute, $second, $month, $d, $year); 1091 $dyOfMo = date('w', $timestamp); 1092 $dayOfMonthCount[$dyOfMo][] = $timestamp; 1093 foreach ($byDayRule as $DAY => $dayCount) 1094 { 1095 if ($DAY == $dyOfMo 1096 && ($dayCount === 0 || $dayCount == sizeof($dayOfMonthCount[$DAY]))) 1097 $dayOccurrences[] = $timestamp; 1098 } 1099 } 1100 // get negative offsets 1101 // 1102 foreach ($byDayRule as $DAY => $dayCount) 1103 { 1104 if ($dayCount < 0) 1105 { 1106 $dayOccurrences[] 1107 = $dayOfMonthCount[$DAY][sizeof($dayOfMonthCount[$DAY]) + $dayCount]; 1108 } 1109 } 1110 $temp_occurrences = array(); 1111 if (empty($occurrences)) $occurrences = $dayOccurrences; 1112 1113 // unless BYMONTHDAY was given too... 1114 // 1115 else if (!empty($dayOccurrences)) 1116 { 1117 foreach ($dayOccurrences as $timestamp) 1118 { 1119 foreach ($occurrences as $occurStamp) 1120 { 1121 1122 // when BYMONTHDAY was given, limit 1123 // occurrence set to the days that 1124 // intersect those month days already 1125 // selected 1126 // 1127 if ($timestamp == $occurStamp) 1128 { 1129 $temp_occurrences[] = $timestamp; 1130 break; 1131 } 1132 } 1133 } 1134 $occurrences = $temp_occurrences; 1135 } 1136 1137 1138 1139 // BYHOUR 1140 // 1141 // Straight-forward... 1142 // 1143 $hourlyOccurrences = array(); 1144 if (!empty($recurrenceRule['BYHOUR'])) for ($d = 1; $d <= $numberOfDaysInMonth; $d++) 1145 { 1146 foreach ($recurrenceRule['BYHOUR'] as $h) 1147 $hourlyOccurrences[] = mktime($h, $minute, $second, $month, $d, $year); 1148 } 1149 $temp_occurrences = array(); 1150 if (empty($occurrences)) $occurrences = $hourlyOccurrences; 1151 1152 // unless one of the above was given too... 1153 // 1154 else if (!empty($hourlyOccurrences)) 1155 { 1156 foreach ($hourlyOccurrences as $timestamp) 1157 { 1158 foreach ($occurrences as $occurStamp) 1159 { 1160 1161 // when BYMONTHDAY or BYDAY were 1162 // given, limit occurrence set to the 1163 // hours that fall in those days 1164 // already selected 1165 // 1166 if (date('d', $timestamp) == date('d', $occurStamp)) 1167 { 1168 $temp_occurrences[] = $timestamp; 1169 break; 1170 } 1171 } 1172 } 1173 $occurrences = $temp_occurrences; 1174 } 1175 1176 1177 1178 // BYMINUTE 1179 // 1180 // Straight-forward, if inefficient... 1181 // 1182 $minuteOccurrences = array(); 1183 if (!empty($recurrenceRule['BYMINUTE'])) for ($d = 1; $d <= $numberOfDaysInMonth; $d++) 1184 { 1185 for ($h = 0; $h < 24; $h++) 1186 foreach ($recurrenceRule['BYMINUTE'] as $mi) 1187 $minuteOccurrences[] = mktime($h, $mi, $second, $month, $d, $year); 1188 } 1189 $temp_occurrences = array(); 1190 if (empty($occurrences)) $occurrences = $minuteOccurrences; 1191 1192 // unless one of the above was given too... 1193 // 1194 else if (!empty($minuteOccurrences)) 1195 { 1196 foreach ($minuteOccurrences as $timestamp) 1197 { 1198 foreach ($occurrences as $occurStamp) 1199 { 1200 1201 // when BYMONTHDAY or BYDAY were 1202 // given, limit occurrence set to the 1203 // minutes that fall in those days 1204 // already selected 1205 // 1206 if (((!empty($recurrenceRule['BYMONTHDAY']) 1207 || !empty($recurrenceRule['BYDAY'])) 1208 && empty($recurrenceRule['BYHOUR']) 1209 && date('d', $timestamp) == date('d', $occurStamp)) 1210 1211 // when BYHOUR was given, no matter what other 1212 // fields are given, limit occurrence set to the 1213 // minutes that fall in those hours already selected 1214 // 1215 || (!empty($recurrenceRule['BYHOUR']) 1216 && date('H', $timestamp) == date('H', $occurStamp))) 1217 { 1218 $temp_occurrences[] = $timestamp; 1219 break; 1220 } 1221 } 1222 } 1223 $occurrences = $temp_occurrences; 1224 } 1225 1226 1227 1228 // BYSECOND 1229 // 1230 // Straight-forward... (but really inefficient) 1231 // 1232 $secondOccurrences = array(); 1233 if (!empty($recurrenceRule['BYSECOND'])) for ($d = 1; $d <= $numberOfDaysInMonth; $d++) 1234 { 1235 for ($h = 0; $h < 24; $h++) 1236 for ($mi = 0; $mi < 60; $mi++) 1237 foreach ($recurrenceRule['BYSECOND'] as $s) 1238 $secondOccurrences[] = mktime($h, $mi, $s, $month, $d, $year); 1239 } 1240 $temp_occurrences = array(); 1241 if (empty($occurrences)) $occurrences = $secondOccurrences; 1242 1243 // unless one of the above was given too... 1244 // 1245 else if (!empty($secondOccurrences)) 1246 { 1247 foreach ($secondOccurrences as $timestamp) 1248 { 1249 foreach ($occurrences as $occurStamp) 1250 { 1251 1252 // when BYMONTHDAY or BYDAY were 1253 // given, limit occurrence set to the 1254 // minutes that fall in those days 1255 // already selected 1256 // 1257 if (((!empty($recurrenceRule['BYMONTHDAY']) 1258 || !empty($recurrenceRule['BYDAY'])) 1259 && empty($recurrenceRule['BYHOUR']) 1260 && empty($recurrenceRule['BYMINUTE']) 1261 && date('d', $timestamp) == date('d', $occurStamp)) 1262 1263 // when BYHOUR was given, no matter what other 1264 // fields are given except BYMINUTE, limit 1265 // occurrence set to the minutes that fall in 1266 // those hours already selected 1267 // 1268 || (!empty($recurrenceRule['BYHOUR']) 1269 && empty($recurrenceRule['BYMINUTE']) 1270 && date('H', $timestamp) == date('H', $occurStamp)) 1271 1272 // when BYMINUTE was given, no matter what other 1273 // fields are given, limit occurrence 1274 // set to the seconds that fall in those minutes already 1275 // selected 1276 // 1277 || (!empty($recurrenceRule['BYMINUTE']) 1278 && date('i', $timestamp) == date('i', $occurStamp))) 1279 { 1280 $temp_occurrences[] = $timestamp; 1281 break; 1282 } 1283 } 1284 } 1285 $occurrences = $temp_occurrences; 1286 } 1287 1288 1289 1290 break; 1291 1292 1293 1294 //--------------------------------------------- 1295 // 1296 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_WEEKLY: 1297 1298 1299 1300 // BYDAY 1301 // 1302 // pick all days in the week that are in BYDAY, 1303 // 1304 for ($timestamp = $currentIntervalBasis; 1305 $timestamp < $currentIntervalBasis + SM_CAL_WEEK_SECONDS; 1306 $timestamp += SM_CAL_DAY_SECONDS) 1307 { 1308//LEFT OFF HERE: here is a possible efficiency improvement: this date() call 1309// and the whole loop look like they can be avoided when BYDAY is empty, yeah? 1310// Are there any other similar improvements in the code elsewhere? 1311 $dyOfWk = date('w', $timestamp); 1312 1313 foreach ($recurrenceRule['BYDAY'] as $DAY) 1314 { 1315 if ($WEEKDAYS[$DAY] == $dyOfWk) 1316 $occurrences[] = $timestamp; 1317 } 1318 1319 // can't let the loop go past end of the week 1320 // (which it will do on the first iteration if 1321 // first occurrence isn't on WKST) 1322 // 1323 if ($dyOfWk == $weekEnd) 1324 break; 1325 1326 } 1327 1328 1329 1330 // BYHOUR 1331 // 1332 // Straight-forward... 1333 // 1334 $hourlyOccurrences = array(); 1335 if (!empty($recurrenceRule['BYHOUR'])) for ($d = $day; $d < $day + 7; $d++) 1336 { 1337 foreach ($recurrenceRule['BYHOUR'] as $h) 1338 $hourlyOccurrences[] = mktime($h, $minute, $second, $month, $d, $year); 1339 } 1340 $temp_occurrences = array(); 1341 if (empty($occurrences)) $occurrences = $hourlyOccurrences; 1342 1343 // unless BYDAY was also given... 1344 // 1345 else if (!empty($hourlyOccurrences)) 1346 { 1347 foreach ($hourlyOccurrences as $timestamp) 1348 { 1349 foreach ($occurrences as $occurStamp) 1350 { 1351 1352 // when BYDAY was given, 1353 // limit occurrence set to the 1354 // hours that fall in those days 1355 // already selected 1356 // 1357 if (date('d', $timestamp) == date('d', $occurStamp)) 1358 { 1359 $temp_occurrences[] = $timestamp; 1360 break; 1361 } 1362 } 1363 } 1364 $occurrences = $temp_occurrences; 1365 } 1366 1367 1368 1369 // BYMINUTE 1370 // 1371 // Straight-forward, if inefficient... 1372 // 1373 $minuteOccurrences = array(); 1374 if (!empty($recurrenceRule['BYMINUTE'])) for ($d = $day; $d < $day + 7; $d++) 1375 { 1376 for ($h = 0; $h < 24; $h++) 1377 foreach ($recurrenceRule['BYMINUTE'] as $mi) 1378 $minuteOccurrences[] = mktime($h, $mi, $second, $month, $d, $year); 1379 } 1380 $temp_occurrences = array(); 1381 if (empty($occurrences)) $occurrences = $minuteOccurrences; 1382 1383 // unless one of the above was given too... 1384 // 1385 else if (!empty($minuteOccurrences)) 1386 { 1387 foreach ($minuteOccurrences as $timestamp) 1388 { 1389 foreach ($occurrences as $occurStamp) 1390 { 1391 1392 // when BYDAY was given, 1393 // limit occurrence set to the 1394 // minutes that fall in those days 1395 // already selected 1396 // 1397 if ((!empty($recurrenceRule['BYDAY']) 1398 && empty($recurrenceRule['BYHOUR']) 1399 && date('d', $timestamp) == date('d', $occurStamp)) 1400 1401 // when BYHOUR was given, no matter what other 1402 // fields are given, limit occurrence set to the 1403 // minutes that fall in those hours already selected 1404 // 1405 || (!empty($recurrenceRule['BYHOUR']) 1406 && date('H', $timestamp) == date('H', $occurStamp))) 1407 { 1408 $temp_occurrences[] = $timestamp; 1409 break; 1410 } 1411 } 1412 } 1413 $occurrences = $temp_occurrences; 1414 } 1415 1416 1417 1418 // BYSECOND 1419 // 1420 // Straight-forward... (but really inefficient) 1421 // 1422 $secondOccurrences = array(); 1423 if (!empty($recurrenceRule['BYSECOND'])) for ($d = $day; $d < $day + 7; $d++) 1424 { 1425 for ($h = 0; $h < 24; $h++) 1426 for ($mi = 0; $mi < 60; $mi++) 1427 foreach ($recurrenceRule['BYSECOND'] as $s) 1428 $secondOccurrences[] = mktime($h, $mi, $s, $month, $d, $year); 1429 } 1430 $temp_occurrences = array(); 1431 if (empty($occurrences)) $occurrences = $secondOccurrences; 1432 1433 // unless one of the above was given too... 1434 // 1435 else if (!empty($secondOccurrences)) 1436 { 1437 foreach ($secondOccurrences as $timestamp) 1438 { 1439 foreach ($occurrences as $occurStamp) 1440 { 1441 1442 // when BYDAY was given, 1443 // limit occurrence set to the 1444 // minutes that fall in those days 1445 // already selected 1446 // 1447 if ((!empty($recurrenceRule['BYDAY']) 1448 && empty($recurrenceRule['BYHOUR']) 1449 && empty($recurrenceRule['BYMINUTE']) 1450 && date('d', $timestamp) == date('d', $occurStamp)) 1451 1452 // when BYHOUR was given, no matter what other 1453 // fields are given except BYMINUTE, limit 1454 // occurrence set to the minutes that fall in 1455 // those hours already selected 1456 // 1457 || (!empty($recurrenceRule['BYHOUR']) 1458 && empty($recurrenceRule['BYMINUTE']) 1459 && date('H', $timestamp) == date('H', $occurStamp)) 1460 1461 // when BYMINUTE was given, no matter what other 1462 // fields are given, limit occurrence 1463 // set to the seconds that fall in those minutes already 1464 // selected 1465 // 1466 || (!empty($recurrenceRule['BYMINUTE']) 1467 && date('i', $timestamp) == date('i', $occurStamp))) 1468 { 1469 $temp_occurrences[] = $timestamp; 1470 break; 1471 } 1472 } 1473 } 1474 $occurrences = $temp_occurrences; 1475 } 1476 1477 1478 1479 break; 1480 1481 1482 1483 //--------------------------------------------- 1484 // 1485 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_DAILY: 1486 1487 1488 1489 // BYHOUR 1490 // 1491 // Straight-forward... 1492 // 1493 foreach ($recurrenceRule['BYHOUR'] as $h) 1494 $occurrences[] = mktime($h, $minute, $second, $month, $day, $year); 1495 1496 1497 1498 // BYMINUTE 1499 // 1500 // Straight-forward, if inefficient... 1501 // 1502 $minuteOccurrences = array(); 1503 if (!empty($recurrenceRule['BYMINUTE'])) 1504 { 1505 for ($h = 0; $h < 24; $h++) 1506 foreach ($recurrenceRule['BYMINUTE'] as $mi) 1507 $minuteOccurrences[] = mktime($h, $mi, $second, $month, $day, $year); 1508 } 1509 $temp_occurrences = array(); 1510 if (empty($occurrences)) $occurrences = $minuteOccurrences; 1511 1512 // unless BYHOUR was given... 1513 // 1514 else if (!empty($minuteOccurrences)) 1515 { 1516 foreach ($minuteOccurrences as $timestamp) 1517 { 1518 foreach ($occurrences as $occurStamp) 1519 { 1520 1521 // when BYHOUR was given, no matter what other 1522 // fields are given, limit occurrence set to the 1523 // minutes that fall in those hours already selected 1524 // 1525 if (date('H', $timestamp) == date('H', $occurStamp)) 1526 { 1527 $temp_occurrences[] = $timestamp; 1528 break; 1529 } 1530 } 1531 } 1532 $occurrences = $temp_occurrences; 1533 } 1534 1535 1536 1537 // BYSECOND 1538 // 1539 // Straight-forward... (but really inefficient) 1540 // 1541 $secondOccurrences = array(); 1542 if (!empty($recurrenceRule['BYSECOND'])) 1543 { 1544 for ($h = 0; $h < 24; $h++) 1545 for ($mi = 0; $mi < 60; $mi++) 1546 foreach ($recurrenceRule['BYSECOND'] as $s) 1547 $secondOccurrences[] = mktime($h, $mi, $s, $month, $day, $year); 1548 } 1549 $temp_occurrences = array(); 1550 if (empty($occurrences)) $occurrences = $secondOccurrences; 1551 1552 // unless one of the above was given too... 1553 // 1554 else if (!empty($secondOccurrences)) 1555 { 1556 foreach ($secondOccurrences as $timestamp) 1557 { 1558 foreach ($occurrences as $occurStamp) 1559 { 1560 1561 // when BYHOUR was given, but not BYMINUTE, 1562 // limit occurrence set to the minutes that 1563 // fall in those hours already selected 1564 // 1565 if ((!empty($recurrenceRule['BYHOUR']) 1566 && empty($recurrenceRule['BYMINUTE']) 1567 && date('H', $timestamp) == date('H', $occurStamp)) 1568 1569 // when BYMINUTE was given, no matter what other 1570 // fields are given, limit occurrence 1571 // set to the seconds that fall in those minutes already 1572 // selected 1573 // 1574 || (!empty($recurrenceRule['BYMINUTE']) 1575 && date('i', $timestamp) == date('i', $occurStamp))) 1576 { 1577 $temp_occurrences[] = $timestamp; 1578 break; 1579 } 1580 } 1581 } 1582 $occurrences = $temp_occurrences; 1583 } 1584 1585 1586 1587 break; 1588 1589 1590 1591 //--------------------------------------------- 1592 // 1593 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_HOURLY: 1594 1595 1596 1597 // BYMINUTE 1598 // 1599 // Straight-forward, if inefficient... 1600 // 1601 foreach ($recurrenceRule['BYMINUTE'] as $mi) 1602 $occurrences[] = mktime($hour, $mi, $second, $month, $day, $year); 1603 1604 1605 1606 // BYSECOND 1607 // 1608 // Straight-forward... (but really inefficient) 1609 // 1610 $secondOccurrences = array(); 1611 if (!empty($recurrenceRule['BYSECOND'])) 1612 { 1613 for ($mi = 0; $mi < 60; $mi++) 1614 foreach ($recurrenceRule['BYSECOND'] as $s) 1615 $secondOccurrences[] = mktime($hour, $mi, $s, $month, $day, $year); 1616 } 1617 $temp_occurrences = array(); 1618 if (empty($occurrences)) $occurrences = $secondOccurrences; 1619 1620 // unless BYMINUTE was given... 1621 // 1622 else if (!empty($secondOccurrences)) 1623 { 1624 foreach ($secondOccurrences as $timestamp) 1625 { 1626 foreach ($occurrences as $occurStamp) 1627 { 1628 1629 // when BYMINUTE was given, 1630 // limit occurrence set to the 1631 // seconds that fall in those 1632 // minutes already selected 1633 // 1634 if (date('i', $timestamp) == date('i', $occurStamp)) 1635 { 1636 $temp_occurrences[] = $timestamp; 1637 break; 1638 } 1639 } 1640 } 1641 $occurrences = $temp_occurrences; 1642 } 1643 1644 1645 1646 break; 1647 1648 1649 1650 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MINUTELY: 1651 1652 1653 1654 // BYSECOND 1655 // 1656 // Straight-forward... 1657 // 1658 foreach ($recurrenceRule['BYSECOND'] as $s) 1659 $occurrences[] = mktime($hour, $minute, $s, $month, $day, $year); 1660 1661 1662 1663 break; 1664 1665 } 1666 1667 1668 1669 // if we found zero occurrences, use the current interval basis itself 1670 // 1671 if (empty($occurrences)) $occurrences = array($currentIntervalBasis); 1672 1673 1674 1675 // sort the occurrence dates 1676 // 1677 else sort($occurrences); 1678 1679 1680 1681 // if BYSETPOS is specified, apply that one last rule here... 1682 // 1683 $newOccurrenceArray = array(); 1684 foreach ($recurrenceRule['BYSETPOS'] as $setpos) 1685 { 1686 if ($setpos < 0) 1687 $setpos = sizeof($occurrences) + $setpos; 1688 $newOccurrenceArray[] = $occurrences[$setpos]; 1689 } 1690 if (!empty($recurrenceRule['BYSETPOS'])) 1691 { 1692 $occurrences = $newOccurrenceArray; 1693 sort($occurrences); 1694 } 1695 1696 1697 // now, run through our set of occurrences, comparing to: 1698 // - the given current occurrence (if any) 1699 // - the UNTIL date, if available 1700 // - the maximum allowable recurrence date 1701 // 1702 $nextOccurrenceDate = 0; 1703 $maxPossibleOccurrenceDate = $occurrences[sizeof($occurrences) - 1]; 1704 $pastEndDate = FALSE; 1705 foreach ($occurrences as $recur) 1706 { 1707 1708 if (!empty($currentOccurrenceDate) && $recur <= $currentOccurrenceDate) 1709 continue; 1710 1711 if (!empty($recurrenceRule['UNTIL']) && $recur > $recurrenceRule['UNTIL']) 1712 { 1713 $pastEndDate = TRUE; 1714 break; 1715 } 1716 1717 if ($recur > $maxDate) 1718 { 1719 $pastEndDate = TRUE; 1720 break; 1721 } 1722 1723 $nextOccurrenceDate = $recur; 1724 break; 1725 1726 } 1727 1728 1729 1730 // check if we have a date from the recurrence date set that comes first 1731 // 1732 $nextRDate = 0; 1733 if (sizeof($recurrenceDates)) 1734 { 1735 1736 // RFC says we can have periods here, but does not 1737 // explain what they mean in this context... does 1738 // end of period override event end time for that 1739 // occurrence? (grrr) Or are we supposed to somehow 1740 // have multiple occurrences within the PERIOD??? (grrr) 1741 // until I find some better documentation, just using 1742 // the starting timestamp as another start date as 1743 // if this PERIOD was just a DATE/TIME stamp 1744 // 1745 if (is_array($recurrenceDates[0])) 1746 $nextRDate = $recurrenceDates[0][0]; 1747 else 1748 $nextRDate = $recurrenceDates[0]; 1749 1750 } 1751 1752 1753 // next recurrence same as next recurrence date? 1754 // remove from RDATE array but don't tell caller 1755 // that it is an RDATE 1756 // 1757 if ($nextOccurrenceDate > 0 && $nextRDate == $nextOccurrenceDate) 1758 { 1759 array_shift($recurrenceDates); 1760 return $nextOccurrenceDate; 1761 } 1762 1763 1764 // if we didn't find a next recurrence and exceeded 1765 // max date, use the next RDATE or return -1 1766 // 1767 if ($nextOccurrenceDate == 0 && $pastEndDate) 1768 { 1769 1770 if ($nextRDate > 0 && $nextRDate <= $maxDate) 1771 { 1772 array_shift($recurrenceDates); 1773 return array($nextRDate); 1774 } 1775 1776 return -1; 1777 1778 } 1779 1780 1781 // if we didn't find a next recurrence, but haven't 1782 // exceeded max dates, check RDATEs or return 0 1783 // 1784 if ($nextOccurrenceDate == 0) 1785 { 1786 1787 if ($nextRDate > 0 && $nextRDate <= $maxPossibleOccurrenceDate) 1788 { 1789 array_shift($recurrenceDates); 1790 return array($nextRDate); 1791 } 1792 1793 return 0; 1794 1795 } 1796 1797 1798 // otherwise, we *did* find a next recurrence, but look 1799 // to see if the next RDATE comes first 1800 // 1801 if ($nextRDate > 0 && $nextRDate < $nextOccurrenceDate) 1802 { 1803 array_shift($recurrenceDates); 1804 return array($nextRDate); 1805 } 1806 1807 1808 return $nextOccurrenceDate; 1809 1810 } 1811 1812 1813 1814 /** 1815 * Utility function for looping through recurring event occurrences 1816 * 1817 * NOTE: DO NOT return from this function without making sure 1818 * $this->cachedOccurrencesThruDate is OK. Usually, at 1819 * least when returning FALSE, this is sufficient: 1820 * $this->cachedOccurrencesThruDate = $maxDate; 1821 * 1822 * @param string $callback A function that will be called 1823 * during each iteration; if this 1824 * callback function returns anything 1825 * but FALSE, this function will cease 1826 * and return that same value. The 1827 * callback function should expect 1828 * two parameters: the timestamp for 1829 * the current event occurrence 1830 * iteration and the $args array. 1831 * Note that the callback function 1832 * must be a function in this class. 1833 * @param array $args An array of arguments to be passed to the 1834 * $callback function (optional) 1835 * @param timestamp $maxDate Date past which we don't 1836 * need to iterate (optional; default 1837 * is the constant MAX_RECURRENCE_DATE, 1838 * as defined in {@link constants.php}) 1839 * NOTE that to avoid time-of-day problems, 1840 * we typically iterate one day past this date 1841 * @param boolean $useExclusionRule If TRUE, the exclusion rule is 1842 * used instead of the regular 1843 * recurrence rule. (optional; 1844 * default=FALSE) 1845 * 1846 * @return mixed Any value returned from the $callback function, 1847 * or FALSE if nothing is returned after iterating 1848 * all occurrences. 1849 * 1850 * @access private 1851 * 1852 */ 1853 function iterateEventOccurrences($callback, $args=array(), $maxDate=MAX_RECURRENCE_DATE, 1854 $useExclusionRule=FALSE) 1855 { 1856 1857 global $color, $week_start_day, $WEEKDAYS; 1858 1859 1860 // add a day to maxDate 1861 // 1862 $maxDate += SM_CAL_DAY_SECONDS; 1863 1864 1865 // which rule set do we use? 1866 // 1867 if (!$useExclusionRule) 1868 { 1869 $recurrenceRule = $this->getRecurrenceRule(); 1870 $recurrenceDates = $this->getRecurrenceDates(); 1871 1872 if (empty($recurrenceRule) && empty($recurrenceDates)) 1873 { 1874 plain_error_message('ERROR (iterateEventOccurrences): Recurring events must have recurrence rule or recurrence dates', $color); 1875 exit; 1876 } 1877 } 1878 else 1879 { 1880 $recurrenceRule = $this->getRecurrenceExclusionRule(); 1881// NOTE: We check elsewhere for EXDATEs by just iterating 1882// them, which is faster than including them here. Make 1883// sure this doesn't mess anything up if EXRULE/EXDATE is 1884// used from another context beside this one (see bottom 1885// of this function) 1886// $recurrenceDates = $this->getRecurrenceExclusionDates(); 1887 $recurrenceDates = array(); 1888 1889 if (empty($recurrenceRule) && empty($recurrenceDates)) 1890 return FALSE; 1891 1892 } 1893 1894 1895 1896 // make sure we have what we need 1897 // 1898 $currentOccurrenceDate = $this->getStartDateTime(); 1899 if (empty($currentOccurrenceDate)) 1900 { 1901 plain_error_message('ERROR (iterateEventOccurrences): Recurring events must have start dates', $color); 1902 exit; 1903 } 1904 1905 1906 // have we already found occurrences up to the given 1907 // max date? if so, use those so we don't have to 1908 // reinvent the wheel - saves a LOT of cycles 1909 // 1910 // only use caching if not working with an exclusion rule 1911//TODO: should build an excluded date list cache too 1912 // 1913 if (!$useExclusionRule) 1914 { 1915 if (sizeof($this->cachedOccurrences) > 0 1916 && ($this->cachedOccurrences[sizeof($this->cachedOccurrences) - 1] >= $maxDate 1917 || $this->cachedOccurrencesThruDate >= $maxDate)) 1918 { 1919 $useCache = TRUE; 1920 $cacheCount = 0; 1921 $currentOccurrenceDate = $this->cachedOccurrences[0]; 1922 } 1923 else // build cache for next time 1924 { 1925 $useCache = FALSE; 1926 $this->cachedOccurrences = array(); 1927 $this->cachedOccurrencesThruDate = 0; 1928 } 1929 } 1930 else 1931 { 1932 $useCache = FALSE; 1933 } 1934 1935 1936 // ready? go... 1937 // 1938 $currentIntervalBasis = $currentOccurrenceDate; 1939 $exclusionDates = $this->getRecurrenceExclusionDates(); 1940 $nextOccurrenceDate = 0; 1941 $nextIntervalBasis = 0; 1942 $count = 1; 1943 $performCallback = TRUE; 1944 while (TRUE) 1945 { 1946 1947 // go do callback 1948 // 1949 if ($performCallback) 1950 { 1951 // build occurrence cache 1952 // 1953 if (!$useCache && !$useExclusionRule) 1954 $this->cachedOccurrences[] = $currentOccurrenceDate; 1955 1956 $retValue = $this->$callback($currentOccurrenceDate, $args); 1957 1958 if ($retValue !== FALSE) 1959 return $retValue; 1960 } 1961 $performCallback = TRUE; 1962 1963 1964 1965 // 1966 // figure out next occurrence 1967 // 1968 1969 1970 1971 // use cache if available 1972 // 1973 if ($useCache) 1974 { 1975 1976 $cacheCount++; 1977 1978 // no occurrences left in cache? bail completely 1979 // 1980 if (!isset($this->cachedOccurrences[$cacheCount])) 1981 return FALSE; 1982 1983 $currentOccurrenceDate = $this->cachedOccurrences[$cacheCount]; 1984 continue; 1985 1986 } 1987 1988 1989 // do we have more occurrences left in the current interval? 1990 // 1991 $foundInRDate = FALSE; 1992 $nextOccurrenceDate = $this->inspectIntraIntervalOccurrences($recurrenceRule, 1993 $recurrenceDates, $currentOccurrenceDate, 1994 $currentIntervalBasis, $maxDate); 1995 1996 1997 // surpassed allowable date range 1998 // 1999 if ($nextOccurrenceDate === -1) 2000 { 2001 $this->cachedOccurrencesThruDate = $maxDate; 2002 return FALSE; 2003 } 2004 2005 2006 2007 // date found in $recurrenceDates? 2008 // 2009 else if (is_array($nextOccurrenceDate)) 2010 { 2011 $foundInRDate = TRUE; 2012 $nextOccurrenceDate = $nextOccurrenceDate[0]; 2013 } 2014 2015 2016 // need to increment our interval 2017 // 2018 else if ($nextOccurrenceDate === 0) while (!empty($recurrenceRule)) 2019 { 2020 2021 2022 // increment by interval until we find a valid occurrence 2023 // 2024 switch ($recurrenceRule['FREQ']) 2025 { 2026 2027 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_SECONDLY: 2028 $nextIntervalBasis = $currentIntervalBasis + $recurrenceRule['INTERVAL']; 2029 break; 2030 2031 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MINUTELY: 2032 $nextIntervalBasis = $currentIntervalBasis 2033 + (60 * $recurrenceRule['INTERVAL']); 2034 break; 2035 2036 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_HOURLY: 2037 $nextIntervalBasis = $currentIntervalBasis 2038 + (3600 * $recurrenceRule['INTERVAL']); 2039 break; 2040 2041 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_DAILY: 2042 $nextIntervalBasis = $currentIntervalBasis 2043 + (SM_CAL_DAY_SECONDS * $recurrenceRule['INTERVAL']); 2044 break; 2045 2046 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_WEEKLY: 2047// LEFT OFF HERE -- WKST: i think each interval jump needs to jump to week starting on WKST day 2048// But does this screw up a simply "once a week" event? 2049// Not sure if this is right (RFC is not clear), but we 2050// move to first occurrence of WKST past today, then jump 2051// the requested number of weeks minus one 2052 if (!isset($recurrenceRule['WKST'])) // don't check if empty, since zero is valid 2053 $weekStart = $week_start_day; 2054 else 2055 $weekStart = $recurrenceRule['WKST']; 2056 for ($x = $currentIntervalBasis + SM_CAL_DAY_SECONDS; 2057 $x <= $currentIntervalBasis + SM_CAL_WEEK_SECONDS; 2058 $x += SM_CAL_DAY_SECONDS) 2059 if (date('w', $x) == $weekStart) 2060 { 2061 $nextIntervalBasis = $x; 2062 break; 2063 } 2064 $nextIntervalBasis = $nextIntervalBasis 2065 + (SM_CAL_WEEK_SECONDS * ($recurrenceRule['INTERVAL'] - 1)); 2066// the old way - just jump one week (times interval): 2067// $nextIntervalBasis = $currentIntervalBasis 2068// + (SM_CAL_WEEK_SECONDS * $recurrenceRule['INTERVAL']); 2069 break; 2070 2071 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MONTHLY: 2072 2073 // incrementing by month is fuzzy; make sure we don't go past 2074 // last day of month when incrementing 2075 // 2076 list($mon, $dy, $yr, $hr, $min, $sec) 2077 = explode('-', date('n-j-Y-G-i-s', $currentIntervalBasis)); 2078 $mon += $recurrenceRule['INTERVAL']; 2079 if ($mon > 12) 2080 { 2081 $overage = $mon - 12; 2082 $yr += floor($overage / 12) + 1; 2083 $mon = $overage - (floor($overage / 12) * 12); 2084 } 2085 $nextIntervalBasis = mktime(12, 0, 0, $mon, 15, $yr); 2086 $lastDayOfMonth = date('t', $nextIntervalBasis); 2087 if ($lastDayOfMonth < $dy) $dy = $lastDayOfMonth; 2088 $nextIntervalBasis = mktime($hr, $min, $sec, $mon, $dy, $yr); 2089 break; 2090 2091 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_YEARLY: 2092 2093 // incrementing by year is also fuzzy... 2094 // 2095 list($mon, $dy, $yr, $hr, $min, $sec) 2096 = explode('-', date('n-j-Y-G-i-s', $currentIntervalBasis)); 2097 $yr += $recurrenceRule['INTERVAL']; 2098 $nextIntervalBasis = mktime(12, 0, 0, $mon, 15, $yr); 2099 $lastDayOfMonth = date('t', $nextIntervalBasis); 2100 if ($lastDayOfMonth < $dy) $dy = $lastDayOfMonth; 2101 $nextIntervalBasis = mktime($hr, $min, $sec, $mon, $dy, $yr); 2102 break; 2103 2104 default: 2105 plain_error_message('ERROR (iterateEventOccurrences): Bad recurrence frequency: ' . $recurrenceRule['FREQ'], $color); 2106 exit; 2107 2108 } 2109 2110 2111 // have we gone past allowable dates? 2112 // stop trying to find another interval 2113 // 2114 // note that the basis date for an interval 2115 // can be as much as a year past the actual 2116 // first occurrence in that interval, thus 2117 // the extended time check (54 weeks just to 2118 // be safe) 2119 // 2120 if ((isset($recurrenceRule['UNTIL']) && !empty($recurrenceRule['UNTIL']) 2121 && ($nextIntervalBasis - (SM_CAL_WEEK_SECONDS * 54)) > $recurrenceRule['UNTIL']) 2122 || ($maxDate < ($nextIntervalBasis - (SM_CAL_WEEK_SECONDS * 54)))) 2123 { 2124 $nextIntervalBasis = $currentIntervalBasis; 2125 break; 2126 } 2127 2128 2129 // now we have to check recurrence rule to make sure 2130 // this interval is allowable (the lack of break 2131 // statements in this switch block is intentional and 2132 // crucial!) 2133 // 2134 switch ($recurrenceRule['FREQ']) 2135 { 2136 2137 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_SECONDLY: 2138 if (!empty($recurrenceRule['BYSECOND']) 2139 && !in_array(date('s', $nextIntervalBasis), $recurrenceRule['BYSECOND'])) 2140 { 2141 $currentIntervalBasis = $nextIntervalBasis; 2142 continue 2; 2143 } 2144 2145 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MINUTELY: 2146 2147 if (!empty($recurrenceRule['BYMINUTE']) 2148 && !in_array(date('i', $nextIntervalBasis), $recurrenceRule['BYMINUTE'])) 2149 { 2150 $currentIntervalBasis = $nextIntervalBasis; 2151 continue 2; 2152 } 2153 2154 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_HOURLY: 2155 2156 if (!empty($recurrenceRule['BYHOUR']) 2157 && !in_array(date('H', $nextIntervalBasis), $recurrenceRule['BYHOUR'])) 2158 { 2159 $currentIntervalBasis = $nextIntervalBasis; 2160 continue 2; 2161 } 2162 2163 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_DAILY: 2164 2165//Note -- for yearly and monthly, this can have a leading number on it that shouldn't be 2166// there now, since we haven't gotten to yearly/monthly yet 2167 if (!empty($recurrenceRule['BYDAY']) 2168 && !in_array(date('w', $nextIntervalBasis), $recurrenceRule['BYDAY'])) 2169 { 2170 $currentIntervalBasis = $nextIntervalBasis; 2171 continue 2; 2172 } 2173 2174 if (!empty($recurrenceRule['BYMONTHDAY'])) 2175 { 2176 2177 // checks positive and negative values of BYMONTHDAY 2178 // 2179 $OK = FALSE; 2180 list($dayOfMonth, $daysInMon) 2181 = explode('-', date('j-t', $nextIntervalBasis)); 2182 foreach ($recurrenceRule['BYMONTHDAY'] as $DAY) 2183 { 2184 2185 if ($DAY < 0) 2186 $DAY = $daysInMon + $DAY + 1; 2187 2188 if ($DAY == $dayOfMonth) 2189 $OK = TRUE; 2190 2191 } 2192 2193 // didn't find a match - can't use this date 2194 // 2195 if (!$OK) 2196 { 2197 $currentIntervalBasis = $nextIntervalBasis; 2198 continue 2; 2199 } 2200 2201 } 2202 2203 if (!empty($recurrenceRule['BYYEARDAY'])) 2204 { 2205 2206 // checks positive and negative values of BYYEARDAY 2207 // 2208 $OK = FALSE; 2209 list($dayOfYear, $yr) 2210 = explode('-', date('z-Y', $nextIntervalBasis)); 2211 $daysInYr = date('z', mktime(12, 0, 0, 12, 31, $yr)); 2212 2213 // we need 1-based numbers, not zero-based numbers: 2214 $dayOfYear++; 2215 $daysInYr++; 2216 2217 foreach ($recurrenceRule['BYYEARDAY'] as $DAY) 2218 { 2219 2220 if ($DAY < 0) 2221 $DAY = $daysInYr + $DAY + 1; 2222 2223 if ($DAY == $dayOfYear) 2224 $OK = TRUE; 2225 2226 } 2227 2228 // didn't find a match - can't use this date 2229 // 2230 if (!$OK) 2231 { 2232 $currentIntervalBasis = $nextIntervalBasis; 2233 continue 2; 2234 } 2235 2236 } 2237 2238//TODO: not entirely clear if these two cases should go above BYYEARDAY 2239 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_WEEKLY: 2240 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MONTHLY: 2241 2242//Note: BYWEEKNO is only applicable to YEARLY frequencies (per RFC), so we skip it here 2243 2244 if (!empty($recurrenceRule['BYMONTH']) 2245 && !in_array(date('m', $nextIntervalBasis), $recurrenceRule['BYMONTH'])) 2246 { 2247 $currentIntervalBasis = $nextIntervalBasis; 2248 continue 2; 2249 } 2250 2251 } 2252 2253 2254 // if we got this far, we successfully found a 2255 // new interval; look for an actual occurrence 2256 // in the interval 2257 // 2258 $currentIntervalBasis = $nextIntervalBasis; 2259 $nextOccurrenceDate = $this->inspectIntraIntervalOccurrences($recurrenceRule, 2260 $recurrenceDates, '', 2261 $currentIntervalBasis, $maxDate); 2262 2263 // found a occurrence from RDATE; exit interval loop 2264 // 2265 if (is_array($nextOccurrenceDate)) 2266 { 2267 $foundInRDate = TRUE; 2268 $nextOccurrenceDate = $nextOccurrenceDate[0]; 2269 break; 2270 } 2271 2272 // surpassed allowable date range exit completely 2273 // 2274 else if ($nextOccurrenceDate === -1) 2275 { 2276 $this->cachedOccurrencesThruDate = $maxDate; 2277 return FALSE; 2278 } 2279 2280 // no occurrences found in this interval; get next interval 2281 // 2282 else if ($nextOccurrenceDate === 0) 2283 continue; 2284 2285 // found a regular occurrence; exit interval loop 2286 // 2287 else 2288 break; 2289 2290 2291 } // end finding new interval 2292 2293 2294 2295 // didn't find a new interval... all we have left is 2296 // to check remaining RDATES 2297 // 2298 if ($nextOccurrenceDate === 0) 2299 { 2300 2301 if ($currentIntervalBasis == $nextIntervalBasis) 2302 { 2303 2304 $rdate = array_shift($recurrenceDates); 2305 2306 2307 // if we couldn't find any more dates at all, 2308 // it's time to just give up and return 2309 // 2310 if (is_null($rdate)) 2311 { 2312 $this->cachedOccurrencesThruDate = $maxDate; 2313 return FALSE; 2314 } 2315 2316 2317 $foundInRDate = TRUE; 2318 2319 2320 // PERIODs will be in the form of array(start/end) 2321 // 2322 if (is_array($rdate)) 2323 { 2324 2325 // RFC says we can have periods here, but does not 2326 // explain what they mean in this context... does 2327 // end of period override event end time for that 2328 // occurrence? (grrr) Or are we supposed to somehow 2329 // have multiple occurrences within the PERIOD??? (grrr) 2330 // until I find some better documentation, just using 2331 // the starting timestamp as another start date as 2332 // if this PERIOD was just a DATE/TIME stamp 2333 // 2334 $nextOccurrenceDate = $rdate[0]; 2335 2336 } 2337 2338 // regular timestamp 2339 // 2340 else 2341 $nextOccurrenceDate = $rdate; 2342 2343 } 2344 else 2345 { 2346 // should not be possible to get here, error if so 2347 // 2348 plain_error_message('ERROR (iterateEventOccurrences): Unknown error', $color); 2349 exit; 2350 } 2351 } 2352 2353 2354 $currentOccurrenceDate = $nextOccurrenceDate; 2355 2356 2357 // check if the new occurrence is on the exclusion list 2358 // 2359 if (!$useExclusionRule) 2360 { 2361 2362 // check EXDATEs 2363 // 2364 foreach ($exclusionDates as $exdate) 2365 { 2366 2367 // if EXDATE type is just DATE (with no time), augment 2368 // with time info from DTSTART (original starting time) 2369 // 2370 if ($this->recurrenceExclusionDates->getType == SM_CAL_ICAL_PROPERTY_TYPE_DATE) 2371 { 2372 list($h, $m, $s) = $this->startTime(); 2373 list($mo, $d, $y) = explode('-', date('n-j-Y', $exdate)); 2374 $exdate = mktime($h, $m, $s, $mo, $d, $y); 2375 } 2376 2377 if ($exdate == $currentOccurrenceDate) 2378 { 2379 $performCallback = FALSE; 2380 break; 2381 } 2382 2383 } 2384 2385 // check EXRULE (only if EXDATE didn't already produce an exclusion) 2386 // 2387 if ($performCallback && $this->startsOnTimestamp($currentOccurrenceDate, TRUE)) 2388 $performCallback = FALSE; 2389 2390 } 2391 2392 2393 // no exclusion found and occurrence comes from RRULE (not RDATE)? 2394 // increment event counter 2395 // 2396 if ($performCallback && !$foundInRDate) $count++; 2397 2398 2399 // check that we haven't exceeded any limits 2400 // 2401 if (isset($recurrenceRule['COUNT']) && !empty($recurrenceRule['COUNT']) 2402 && $count > $recurrenceRule['COUNT']) 2403 { 2404 $this->cachedOccurrencesThruDate = $maxDate; 2405 return FALSE; 2406 } 2407 if ($maxDate < $currentOccurrenceDate) 2408 { 2409 $this->cachedOccurrencesThruDate = $maxDate; 2410 return FALSE; 2411 } 2412 2413 2414 } // end main (endless) loop 2415 2416 } 2417 2418 2419 2420 /** 2421 * Dummy callback for use when forcing an event occurrence cache. 2422 * 2423 * Also constructs date-based cache information. 2424 * 2425 * This function is intended as a callback function for iterateEventOccurrences() 2426 * 2427 * @param timestamp $timestamp The date of the occurrence 2428 * 2429 * @return boolean FALSE 2430 * 2431 * @access private 2432 * 2433 */ 2434 function forceEventOccurrenceCacheCallback($timestamp) 2435 { 2436 2437 list($day, $month, $year) = explode(',', date('d,m,Y', $timestamp)); 2438 $this->startDateCache[$year][intval($month)][intval($day)] = array( 2439 'timestamp' => $timestamp, 2440 ); 2441 2442 $endTime = $timestamp + $this->getEndDateTime() - $this->getStartDateTime(); 2443 list($day, $month, $year) = explode(',', date('d,m,Y', $endTime)); 2444 $this->endDateCache[$year][intval($month)][intval($day)] = array( 2445 'timestamp' => $endTime, 2446 ); 2447 2448 return FALSE; 2449 2450 } 2451 2452 2453 2454 /** 2455 * Determines if an event occurrence is on the given day. 2456 * 2457 * This function is intended as a callback function for iterateEventOccurrences() 2458 * 2459 * @param timestamp $timestamp The date of the occurrence 2460 * @param array $date The day being checked, this array contains four 2461 * integer entries: year, month and day as well as 2462 * the timestamp generated from the same values. 2463 * 2464 * @return boolean TRUE if this event occurrence is on the given day, FALSE otherwise. 2465 * 2466 * @access private 2467 * 2468 */ 2469 function checkOccurrence($timestamp, $date) 2470 { 2471 2472 return dayIsBetween($date[0], $date[1], $date[2], 2473 $timestamp, 2474//NOTE: we cheat by reaching into the startDateTime Property object directly, 2475// which violates object integrity, but it gains a few tenths of a second 2476// (is that worth it?) 2477 $timestamp + $this->getEndDateTime() - $this->startDateTime->value, 2478// This is what it *should* be: 2479// $timestamp + $this->getEndDateTime() - $this->getStartDateTime(), 2480 $date[3]); 2481 2482 } 2483 2484 2485 2486 /** 2487 * Determines if an event occurrence is on the given timestamp. 2488 * 2489 * This function is intended as a callback function for iterateEventOccurrences() 2490 * 2491 * @param timestamp $timestamp The date of the occurrence 2492 * @param array $timestampToCheck The timestamp being checked; a one-element array 2493 * containing the timestamp 2494 * 2495 * @return boolean TRUE if this event occurrence is on the given timestamp, FALSE otherwise. 2496 * 2497 * @access private 2498 * 2499 */ 2500 function checkOccurrenceForTimestamp($timestamp, $timestampToCheck) 2501 { 2502 2503 if ($timestamp <= $timestampToCheck[0] 2504 && $timestampToCheck[0] <= $timestamp + $this->getEndDateTime() - $this->getStartDateTime()) 2505 return TRUE; 2506 2507 return FALSE; 2508 2509 } 2510 2511 2512 2513 /** 2514 * Determines if an event occurrence starts on the given timestamp. 2515 * 2516 * This function is intended as a callback function for iterateEventOccurrences() 2517 * 2518 * @param timestamp $timestamp The date of the occurrence 2519 * @param array $timestampToCheck The timestamp being checked; a one-element array 2520 * containing the timestamp 2521 * 2522 * @return boolean TRUE if this event occurrence starts on the 2523 * given timestamp, FALSE otherwise. 2524 * 2525 * @access private 2526 * 2527 */ 2528 function checkStartsOnTimestamp($timestamp, $timestampToCheck) 2529 { 2530 2531 if ($timestamp == $timestampToCheck[0]) 2532 return TRUE; 2533 2534 return FALSE; 2535 2536 } 2537 2538 2539 2540 /** 2541 * Determines if an event occurrence starts on the given day. 2542 * 2543 * This function is intended as a callback function for iterateEventOccurrences() 2544 * 2545 * @param timestamp $timestamp The date of the occurrence 2546 * @param array $date The day being checked, this array contains three 2547 * integer entries: year, month and day plus an 2548 * optional fourth entry that is the value of getdate() 2549 * for this date (provides speed boost). 2550 * 2551 * @return boolean TRUE if this event occurrence starts on the given day, FALSE otherwise. 2552 * 2553 * @access private 2554 * 2555 */ 2556 function checkStartsOnDay($timestamp, $date) 2557 { 2558 2559 $startInfo = getdate($timestamp); 2560 2561 if (isset($date[3]) && !empty($date[3])) 2562 $dayInfo = $date[3]; 2563 else 2564 $dayInfo = getdate(mktime(0, 0, 0, $date[1], $date[2], $date[0])); 2565 2566 if ($startInfo['year'] == $date[0] && $startInfo['yday'] == $dayInfo['yday']) 2567 return TRUE; 2568 2569 return FALSE; 2570 2571 } 2572 2573 2574 2575 /** 2576 * Determines if an event occurrence ends on the given day. 2577 * 2578 * This function is intended as a callback function for iterateEventOccurrences() 2579 * 2580 * @param timestamp $timestamp The date of the occurrence 2581 * @param array $date The day being checked, this array contains three 2582 * integer entries: year, month and day plus an 2583 * optional fourth entry that is the value of getdate() 2584 * for this date (provides speed boost). 2585 * 2586 * @return boolean TRUE if this event occurrence ends on the given day, FALSE otherwise. 2587 * 2588 * @access private 2589 * 2590 */ 2591 function checkEndsOnDay($timestamp, $date) 2592 { 2593 2594 $endInfo = getdate($timestamp + $this->getEndDateTime() - $this->getStartDateTime()); 2595 2596 if (isset($date[3]) && !empty($date[3])) 2597 $dayInfo = $date[3]; 2598 else 2599 $dayInfo = getdate(mktime(0, 0, 0, $date[1], $date[2], $date[0])); 2600 2601 if ($endInfo['year'] == $date[0] && $endInfo['yday'] == $dayInfo['yday']) 2602 return TRUE; 2603 2604 return FALSE; 2605 2606 } 2607 2608 2609 2610 /** 2611 * Finds the starting date for a given event occurrence 2612 * 2613 * This function is intended as a callback function for iterateEventOccurrences() 2614 * 2615 * @param timestamp $timestamp The date of the occurrence 2616 * @param array $date The day being checked, this array contains three 2617 * integer entries: year, month and day. 2618 * 2619 * @return mixed The timestamp of the event occurrence that matches 2620 * the given day, FALSE otherwise. 2621 * 2622 * @access private 2623 * 2624 */ 2625 function findOccurrenceStartDate($timestamp, $date) 2626 { 2627 2628 if (dayIsBetween($date[0], $date[1], $date[2], 2629//NOTE: we cheat by reaching into the startDateTime Property object directly, 2630// which violates object integrity, but it gains a few tenths of a second 2631// (is that worth it?) 2632 $timestamp, $timestamp + $this->getEndDateTime() - $this->startDateTime->value)) 2633// This is what it *should* be: 2634// $timestamp, $timestamp + $this->getEndDateTime() - $this->getStartDateTime())) 2635 return $timestamp; 2636 2637 return FALSE; 2638 2639 } 2640 2641 2642 2643// ---------- PUBLIC ---------- 2644 2645 2646 2647 /** 2648 * Cache event occurrences 2649 * 2650 * It is highly recommended to use this function when first 2651 * instantiating a recurring event in order to cut down on 2652 * having to run the recurrence engine, for example, for every 2653 * day in a month. In such a scenario, the best thing to do 2654 * is call this method with a date a few days into the 2655 * following month. 2656 * 2657 * Also supported is session-based cross-request caching, 2658 * which is also highly recommended. Event times are stored 2659 * between HTTP requests, so the recursion engine only needs 2660 * to be run when an event is changed or when the requested 2661 * date is outside of the current cache (or when starting day 2662 * of week is changed). 2663 * 2664 * @param int $maxCacheDate The timestamp through which to 2665 * force an occurrence cache 2666 * @param boolean $cacheInSession When TRUE, event times are 2667 * also stored in session. 2668 * (optional; default = TRUE) 2669 * 2670 * @access public 2671 * 2672 */ 2673 function forceEventOccurrenceCache($maxCacheDate, $cacheInSession=TRUE) 2674 { 2675 2676 // see if events are already stored in session if allowed 2677 // 2678 if ($cacheInSession) 2679 { 2680 global $calendarCache, $week_start_day; 2681 sqgetGlobalVar('calendarCache', $calendarCache, SQ_SESSION); 2682 if (isset($calendarCache[$this->getID()]) && !empty($calendarCache[$this->getID()])) 2683 { 2684 2685 // cache is only valid if event has not been modified since it 2686 // was stored in session and if the starting day of the week has 2687 // not changed 2688 // 2689 $eventLastModified = $calendarCache[$this->getID()]['lastUpdatedOn']; 2690 $lastWeekStartDay = $calendarCache[$this->getID()]['week_start_day']; 2691 if ($eventLastModified == $this->lastUpdatedOn() 2692 && $lastWeekStartDay == $week_start_day) 2693 { 2694 $this->startDateCache 2695 = $calendarCache[$this->getID()]['startDateCache']; 2696 $this->endDateCache 2697 = $calendarCache[$this->getID()]['endDateCache']; 2698 $this->cachedOccurrences 2699 = $calendarCache[$this->getID()]['cachedOccurrences']; 2700 $this->cachedOccurrencesThruDate 2701 = $calendarCache[$this->getID()]['cachedOccurrencesThruDate']; 2702 } 2703 } 2704 } 2705 2706 2707 // go figure out the cache dates 2708 // 2709 $this->iterateEventOccurrences('forceEventOccurrenceCacheCallback', 2710 array(), $maxCacheDate); 2711 2712 2713 // if we need to store events in session, do so here 2714 // (overwrite anything that was there, because when 2715 // iterating above, it is entirely possible for the 2716 // cache to have had to been rebuilt due to different 2717 // max date) 2718 // 2719 if ($cacheInSession) 2720 { 2721 2722 $calendarCache[$this->getID()]['startDateCache'] 2723 = $this->startDateCache; 2724 $calendarCache[$this->getID()]['endDateCache'] 2725 = $this->endDateCache; 2726 $calendarCache[$this->getID()]['cachedOccurrences'] 2727 = $this->cachedOccurrences; 2728 $calendarCache[$this->getID()]['cachedOccurrencesThruDate'] 2729 = $this->cachedOccurrencesThruDate; 2730 $calendarCache[$this->getID()]['lastUpdatedOn'] 2731 = $this->lastUpdatedOn(); 2732 $calendarCache[$this->getID()]['week_start_day'] 2733 = $week_start_day; 2734 2735 sqsession_register($calendarCache, 'calendarCache'); 2736 2737 } 2738//for testing - remove cache from session: 2739//sqsession_unregister('calendarCache'); 2740 2741 } 2742 2743 2744 2745 /** 2746 * Reset Permissions To Match Parent Calendar 2747 * 2748 * Wipes any permissions for this event and resets 2749 * them to match whatever is in the parent calendar, 2750 * including copying the creator (of the last parent 2751 * calendar found). 2752 * 2753 * @param array $parents An array of the parent calendars, 2754 * used to avoid the need to look 2755 * up the parent calendars in the 2756 * backend (which, if the event's 2757 * parent calendar(s) is(have) not 2758 * saved, will cause backend errors). 2759 * (optional, default empty (parents 2760 * are looked up in backend)) 2761 * 2762 * @access public 2763 * 2764 */ 2765 function resetPermissionsToParent($parents=array()) 2766 { 2767 2768 // loop thru all parent calendars, copy their owners, 2769 // readable and writeable users into this event 2770 // 2771 $o = array(); 2772 $w = array(); 2773 $r = array(); 2774 $c = ''; 2775 foreach ($this->getParentCalendars() as $parentID) 2776 { 2777 2778 // check for parent in given array first 2779 // 2780 $lookup = TRUE; 2781 foreach ($parents as $parent) 2782 if ($parent->getID() == $parentID) 2783 { 2784 $lookup = FALSE; 2785 break; 2786 } 2787 2788 if ($lookup) 2789 $parent = get_calendar($parentID, TRUE); 2790 2791 2792 // if there was no parent calendar found, the event and its 2793 // parent are not yet in the backend, so we'll just skip this 2794 // for now 2795 // 2796 // not ideal, but we hope the event will get its permissions 2797 // reset later... 2798 // 2799 if ($parent === FALSE) continue; 2800 2801 2802 $o = array_unique(array_merge($o, $parent->getOwners())); 2803 $w = array_unique(array_merge($w, $parent->getWriteableUsers())); 2804 $r = array_unique(array_merge($r, $parent->getReadableUsers())); 2805 $c = $parent->createdBy(); 2806 } 2807 $this->owners->setValue($o); 2808 $this->writeable_users->setValue($w); 2809 $this->readable_users->setValue($r); 2810 $this->setCreator($c); 2811 2812 } 2813 2814 2815 2816 /** 2817 * Get Event ID 2818 * 2819 * @return string This event's internal ID 2820 * 2821 * @access public 2822 * 2823 */ 2824 function getID() 2825 { 2826 return $this->id->getValue(); 2827 } 2828 2829 2830 2831 /** 2832 * Set Event ID 2833 * 2834 * @param string $id The new internal ID to be assigned to this event 2835 * 2836 * @access public 2837 * 2838 */ 2839 function setID($id) 2840 { 2841 $this->id->setValue($id); 2842 } 2843 2844 2845 2846 /** 2847 * Get Sequence Number 2848 * 2849 * @return int This event's sequence number 2850 * 2851 * @access public 2852 * 2853 */ 2854 function getSequence() 2855 { 2856 return $this->sequence->getValue(); 2857 } 2858 2859 2860 2861 /** 2862 * Increment Sequence Number 2863 * 2864 * @access public 2865 * 2866 */ 2867 function incrementSequence() 2868 { 2869 $this->sequence->setValue($this->sequence->getValue() + 1); 2870 } 2871 2872 2873 2874 /** 2875 * Get Event Title 2876 * 2877 * Returns event summary if available, otherwise a truncated 2878 * version of the description is returned, and if the description 2879 * is also empty, this event's UID is returned 2880 * 2881 * @return string This event's title 2882 * 2883 * @access public 2884 * 2885 */ 2886 function getTitle() 2887 { 2888 $ret = $this->getSummary(); 2889 if (empty($ret)) 2890 { 2891 $ret = $this->getDescription(); 2892 $ret = preg_replace("/(\r\n)|(\n)|(\r)/", '', $ret); 2893 $ret = substr($ret, 0, 10) . _("..."); 2894 if (empty($ret)) 2895 $ret = $this->getID(); 2896 } 2897 return $ret; 2898 } 2899 2900 2901 2902 /** 2903 * Get Event Summary 2904 * 2905 * @return string This event's summary 2906 * 2907 * @access public 2908 * 2909 */ 2910 function getSummary() 2911 { 2912 return $this->summary->getValue(); 2913 } 2914 2915 2916 2917 /** 2918 * Get Event Description 2919 * 2920 * @return string This event's description 2921 * 2922 * @access public 2923 * 2924 */ 2925 function getDescription() 2926 { 2927 return $this->description->getValue(); 2928 } 2929 2930 2931 2932 /** 2933 * Get Event Comments 2934 * 2935 * @return string This event's comments 2936 * 2937 * @access public 2938 * 2939 */ 2940 function getComments() 2941 { 2942 return $this->comments->getValue(); 2943 } 2944 2945 2946 2947 /** 2948 * Get Event Domain 2949 * 2950 * @return string This event's domain 2951 * 2952 * @access public 2953 * 2954 */ 2955 function getDomain() 2956 { 2957 return $this->dom->getValue(); 2958 } 2959 2960 2961 2962 /** 2963 * Get Event Status 2964 * 2965 * @return string This event's status, which should 2966 * correspond to the event or todo 2967 * status constants defined in {@link constants.php} 2968 * 2969 * @access public 2970 * 2971 */ 2972 function getStatus() 2973 { 2974 return $this->status->getValue(); 2975 } 2976 2977 2978 2979 /** 2980 * Get Event Type 2981 * 2982 * @return string This event's type, which will 2983 * correspond to the event type 2984 * constants defined in {@link constants.php} 2985 * 2986 * @access public 2987 * 2988 */ 2989 function getEventType() 2990 { 2991 return $this->type->getValue(); 2992 } 2993 2994 2995 2996 /** 2997 * Get Internal Start Date/Time 2998 * 2999 * @return int The timestamp representing this event's start date/time 3000 * 3001 * @access public 3002 * 3003 */ 3004 function getStartDateTime() 3005 { 3006 3007 return $this->startDateTime->getValue(); 3008 3009 } 3010 3011 3012 3013 /** 3014 * Determines if this event is an all-day/anniversary event 3015 * 3016 * @return boolean TRUE if this event is an all-day event, FALSE otherwise 3017 * 3018 * @access public 3019 * 3020 */ 3021 function isAllDay() 3022 { 3023 return ($this->startDateTime->getType() == SM_CAL_ICAL_PROPERTY_TYPE_DATE); 3024 } 3025 3026 3027 3028 /** 3029 * Determines if this event is transparent for busy time searches 3030 * 3031 * @return boolean TRUE if this event is transparent, FALSE otherwise (opaque) 3032 * 3033 * @access public 3034 * 3035 */ 3036 function isTransparent() 3037 { 3038 3039 // all-day/anniversary type events are always transparent 3040 // 3041 if ($this->isAllDay()) return TRUE; 3042 3043 3044 // TODO: we don't yet support the TRANSP item for VEVENTS, but 3045 // from here on, we just return its value if it exists for this 3046 // event, and otherwise return false 3047 3048 3049 return FALSE; 3050 3051 } 3052 3053 3054 3055 /** 3056 * Get Internal End Date/Time 3057 * 3058 * @return int The timestamp representing this event's end date/time 3059 * 3060 * @access public 3061 * 3062 */ 3063 function getEndDateTime() 3064 { 3065 3066// if ($this->cachedEndDateTime !== '') return $this->cachedEndDateTime; 3067 if (!empty($this->cachedEndDateTime)) return $this->cachedEndDateTime; 3068//LEFT OFF HERE: is it possible that any of the values used herein 3069// to determine the return value will change in the 3070// course of a page request and the cached value will 3071// become invalid? do we need to clear the cached 3072// value wherever the dependencies herein get changed? 3073 3074 3075 // duration trumps all 3076 // 3077 $dur = $this->getDuration(); 3078 if (!empty($dur)) 3079 { 3080 $this->cachedEndDateTime = $dur + $this->getStartDateTime(); 3081 return $this->cachedEndDateTime; 3082 } 3083 3084 3085 // Tasks/Todos have "due date" instead of end date 3086 // 3087 if ($this->isTask()) 3088 { 3089 $this->cachedEndDateTime = $this->getDueDateTime(); 3090 return $this->cachedEndDateTime; 3091 } 3092 3093 3094 $endValue = $this->endDateTime->getValue(); 3095 3096 3097 // when start date is just a DATE, this is an 3098 // "anniversary" (all-day) type event; end 3099 // date/time will always be the end of a day 3100 // 3101 if ($this->isAllDay()) 3102 { 3103 3104 // if no end date is available, it is the same day as the start 3105 // 3106 if (empty($endValue)) 3107 { 3108 $endValue = $this->getStartDateTime(); 3109 } 3110 3111 // otherwise, we have to subtract a day from the end, since 3112 // end time is always non-inclusive 3113 // 3114 else 3115 { 3116 $endValue -= SM_CAL_DAY_SECONDS; 3117 } 3118 3119 // set end time to last minute/second of the day 3120 // 3121 list($y, $m, $d) = explode('-', date('Y-n-j', $endValue)); 3122 $endValue = mktime(23, 59, 59, $m, $d, $y); 3123 3124 } 3125 3126 3127 // if no end date is given for regular (non-anniversary/all-day) 3128 // events, it gets set to the same time as the event start 3129 // 3130 else if (empty($endValue)) 3131 $endValue = $this->startDateTime->getValue(); 3132 3133 3134 $this->cachedEndDateTime = $endValue; 3135 return $endValue; 3136 3137 } 3138 3139 3140 3141 /** 3142 * Get Due Date 3143 * 3144 * @return int The timestamp representing this todo/taks's due date 3145 * 3146 * @access public 3147 * 3148 */ 3149 function getDueDateTime() 3150 { 3151 return $this->due->getValue(); 3152 } 3153 3154 3155 3156 /** 3157 * Get Duration 3158 * 3159 * @return int The number of seconds corresponding to this event's duration 3160 * 3161 * @access public 3162 * 3163 */ 3164 function getDuration() 3165 { 3166 return $this->duration->getValue(); 3167 } 3168 3169 3170 3171 /** 3172 * Get Recurrence Rule 3173 * 3174 * @return array The (parsed) recurrence rule for this event 3175 * 3176 * @access public 3177 * 3178 */ 3179 function getRecurrenceRule() 3180 { 3181 return $this->recurrenceRule->getValue(); 3182 } 3183 3184 3185 3186 /** 3187 * Get Recurrence Days, if available 3188 * 3189 * @return mixed An array of the days of the week this event 3190 * is set to occur on (each array element is a 3191 * weekday constant as defined in {@link constants.php}. 3192 * If none are set for this event, FALSE is returned. 3193 * 3194 * @access public 3195 * 3196 */ 3197 function getRecurrenceDays() 3198 { 3199 3200 $rrule = $this->getRecurrenceRule(); 3201 3202 if (!empty($rrule) && isset($rrule['BYDAY']) && !empty($rrule['BYDAY'])) 3203 { 3204 global $WEEKDAYS; 3205 $returnArray = array(); 3206//TODO: is this just a waste of CPU cycles? we could just return the 3207// array of day abbreviations (strings such as TU, WE, TH, etc) 3208// and let the caller convert to day constants only if needed...?? 3209 foreach ($rrule['BYDAY'] as $day) 3210 $returnArray[] = $WEEKDAYS[$day]; 3211 return $returnArray; 3212 } 3213 3214 return FALSE; 3215 3216 } 3217 3218 3219 3220 /** 3221 * Get Recurrence Interval, if available 3222 * 3223 * @return mixed The recurrence interval (integer) if 3224 * recurrence rule is available and has 3225 * INTERVAL clause; FALSE otherwise 3226 * 3227 * @access public 3228 * 3229 */ 3230 function getRecurrenceInterval() 3231 { 3232 3233 $rrule = $this->getRecurrenceRule(); 3234 3235 if (!empty($rrule) && isset($rrule['INTERVAL']) && !empty($rrule['INTERVAL'])) 3236 return $rrule['INTERVAL']; 3237 3238 return FALSE; 3239 3240 } 3241 3242 3243 3244 /** 3245 * Get Recurrence "Type" 3246 * 3247 * If this is a recurring event, give back the GUI-based 3248 * recurrence type that corresponds to the RRULE's FREQ 3249 * clause, or return FALSE if this is a non-recurring event. 3250 * 3251 * @return mixed The GUI-based recurrence type, as defined 3252 * by the event recurrence type constants 3253 * defined in {@link constants.php}, or FALSE 3254 * if this is a one-time event 3255 * 3256 * @access public 3257 * 3258 */ 3259 function getRecurrenceType() 3260 { 3261 3262 global $color; 3263 3264 if ($this->isOneTime()) 3265 return FALSE; 3266 3267 $rrule = $this->getRecurrenceRule(); 3268 3269 switch ($rrule['FREQ']) 3270 { 3271 3272//TODO: some day, support hourly/minutely/secondly events 3273 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_SECONDLY: 3274 plain_error_message('ERROR: The editing of events recurring SECONDLY not currently supported', $color); 3275 exit; 3276 3277 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MINUTELY: 3278 plain_error_message('ERROR: The editing of events recurring MINUTELY not currently supported', $color); 3279 exit; 3280 3281 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_HOURLY: 3282 plain_error_message('ERROR: The editing of events recurring HOURLY not currently supported', $color); 3283 exit; 3284 3285 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_DAILY: 3286 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_WEEKLY: 3287 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_MONTHLY: 3288 case SM_CAL_ICAL_EVENT_RECURRENCE_FREQ_YEARLY: 3289 return $rrule['FREQ']; 3290 3291 } 3292 3293 } 3294 3295 3296 3297 /** 3298 * Get Recurrence Date(s) 3299 * 3300 * NOTE: careful when getting value of RDATE, as it can be an 3301 * array of arrays (PERIOD start/end timestamps) *OR* 3302 * an array with just one PERIOD, *OR* an array of 3303 * regular timestamps! Always check the type! 3304 * $this->recurrenceDates->getType 3305 * SM_CAL_ICAL_PROPERTY_TYPE_PERIOD 3306 * or one of the date/time types 3307 * 3308 * @return mixed The recurrence date(s) for this event (int or array of ints) 3309 * 3310 * @access public 3311 * 3312 */ 3313 function getRecurrenceDates() 3314 { 3315 return $this->recurrenceDates->getValue(); 3316 } 3317 3318 3319 3320 /** 3321 * Get Recurrence Exclusion Rule 3322 * 3323 * @return string The recurrence exclusion rule for this event 3324 * 3325 * @access public 3326 * 3327 */ 3328 function getRecurrenceExclusionRule() 3329 { 3330 return $this->recurrenceExclusionRule->getValue(); 3331 } 3332 3333 3334 3335 /** 3336 * Get Recurrence Exclusion Date(s) 3337 * 3338 * @return mixed The recurrence exclusion date(s) for this event (int or array of ints) 3339 * 3340 * @access public 3341 * 3342 */ 3343 function getRecurrenceExclusionDates() 3344 { 3345 return $this->recurrenceExclusionDates->getValue(); 3346 } 3347 3348 3349 3350 /** 3351 * Get Percent Complete 3352 * 3353 * @return int The percent value of completion for this todo/task 3354 * 3355 * @access public 3356 * 3357 */ 3358 function getPercentComplete() 3359 { 3360 return $this->percentComplete->getValue(); 3361 } 3362 3363 3364 3365 /** 3366 * Determines if this event is recurring 3367 * 3368 * @return boolean TRUE if this event is recurring, FALSE otherwise 3369 * 3370 * @access public 3371 * 3372 */ 3373 function isRecurring() 3374 { 3375//TODO: does this logic hold up once we add todo/task types? 3376 return (!$this->isOneTime()); 3377 } 3378 3379 3380 3381 /** 3382 * Determines if this event is one time (not recurring) 3383 * 3384 * @return boolean TRUE if this event is one-time, FALSE otherwise 3385 * 3386 * @access public 3387 * 3388 */ 3389 function isOneTime() 3390 { 3391 $rrule = $this->getRecurrenceRule(); 3392 $rdates = $this->getRecurrenceDates(); 3393 return (empty($rrule) && empty($rdates)); 3394 } 3395 3396 3397 3398 /** 3399 * Determines if this event is a task/todo item 3400 * 3401 * @return boolean TRUE if this event is a task/todo item, FALSE otherwise 3402 * 3403 * @access public 3404 * 3405 */ 3406 function isTask() 3407 { 3408 return ($this->getEventType() == SM_EVENT_TYPE_TODO); 3409 } 3410 3411 3412 3413 /** 3414 * Set Event Priority 3415 * 3416 * @param int $priority The new event priority to be assigned to this event 3417 * 3418 * @access public 3419 * 3420 */ 3421 function setPriority($priority) 3422 { 3423 $this->priority->setValue($priority); 3424 } 3425 3426 3427 3428 /** 3429 * Get Event Priority 3430 * 3431 * @return int This event's priority 3432 * 3433 * @access public 3434 * 3435 */ 3436 function getPriority() 3437 { 3438 return $this->priority->getValue(); 3439 } 3440 3441 3442 3443 /** 3444 * Get Parent Calendars 3445 * 3446 * @return array A list of this event's parent calendar IDs 3447 * 3448 * @access public 3449 * 3450 */ 3451 function getParentCalendars() 3452 { 3453 $p = $this->parentCalendars->getValue(); 3454 if (empty($p)) 3455 return array(); 3456 else if (is_string($p)) 3457 return array($p); 3458 else 3459 return $p; 3460 } 3461 3462 3463 3464 /** 3465 * Reset Parent Calendars 3466 * 3467 * Clears all parent calendars - use with care! 3468 * 3469 * @access public 3470 * 3471 */ 3472 function resetParentCalendars() 3473 { 3474 $this->parentCalendars->setValue(array()); 3475 } 3476 3477 3478 3479 /** 3480 * Add Parent Calendar 3481 * 3482 * Note that duplicates are automatically weeded out. 3483 * 3484 * @param string $id The ID of the parent calendar to be added 3485 * 3486 * @access public 3487 * 3488 */ 3489 function addParentCalendar($id) 3490 { 3491 $p = $this->getParentCalendars(); 3492 if (!in_array($id, $p)) $p[] = $id; 3493 $this->parentCalendars->setValue($p); 3494 } 3495 3496 3497 3498 /** 3499 * Remove Parent Calendar 3500 * 3501 * @param string $id The ID of the parent calendar to be removed 3502 * 3503 * @access public 3504 * 3505 */ 3506 function removeParentCalendar($id) 3507 { 3508 $p = $this->getParentCalendars(); 3509 $p = array_diff($p, array($id)); 3510 $this->parentCalendars->setValue($p); 3511 } 3512 3513 3514 3515 /** 3516 * Get Creator Name 3517 * 3518 * @return string This event's creator 3519 * 3520 * @access public 3521 * 3522 */ 3523 function createdBy() 3524 { 3525 return $this->createdBy->getValue(); 3526 } 3527 3528 3529 3530 /** 3531 * Set Username of User Who Created This Event 3532 * 3533 * @param string $user The name of the user who created this event 3534 * 3535 * @access public 3536 * 3537 */ 3538 function setCreator($user) 3539 { 3540 return $this->createdBy->setValue($user); 3541 } 3542 3543 3544 3545 /** 3546 * Get Creation Date 3547 * 3548 * @return timestamp This event's creation date 3549 * 3550 * @access public 3551 * 3552 */ 3553 function createdOn() 3554 { 3555 return $this->createdOn->getValue(); 3556 } 3557 3558 3559 3560 /** 3561 * Get User That Last Updated Event 3562 * 3563 * @return string This event's last editor 3564 * 3565 * @access public 3566 * 3567 */ 3568 function lastUpdatedBy() 3569 { 3570 return $this->lastUpdatedBy->getValue(); 3571 } 3572 3573 3574 3575 /** 3576 * Set Username of User Who Last Updated This Event 3577 * 3578 * @param string $user The name of the user updating this event 3579 * 3580 * @access public 3581 * 3582 */ 3583 function setLastUpdator($user) 3584 { 3585 return $this->lastUpdatedBy->setValue($user); 3586 } 3587 3588 3589 3590 /** 3591 * Get Last Update Date 3592 * 3593 * @return timestamp The date of this event's last update 3594 * 3595 * @access public 3596 * 3597 */ 3598 function lastUpdatedOn() 3599 { 3600 return $this->lastUpdatedOn->getValue(); 3601 } 3602 3603 3604 3605 /** 3606 * Set Last Update Date 3607 * 3608 * @param string $timestamp The date this calendar was last updated 3609 * (should be a UTC-formatted date/time string) 3610 * 3611 * @access public 3612 * 3613 */ 3614 function setLastUpdateDate($timestamp) 3615 { 3616 return $this->lastUpdatedOn->setValue($timestamp); 3617 } 3618 3619 3620 3621 /** 3622 * Get Event Owners 3623 * 3624 * @return array An array listing all event owners 3625 * 3626 * @access public 3627 * 3628 */ 3629 function getOwners() 3630 { 3631 $o = $this->owners->getValue(); 3632 if (empty($o)) 3633 return array(); 3634 else if (is_string($o)) 3635 return array($o); 3636 else 3637 return $o; 3638 } 3639 3640 3641 3642 /** 3643 * Get Readable Users 3644 * 3645 * @return array An array listing all users 3646 * who have read access to this event 3647 * 3648 * @access public 3649 * 3650 */ 3651 function getReadableUsers() 3652 { 3653 $r = $this->readable_users->getValue(); 3654 if (empty($r)) 3655 return array(); 3656 else if (is_string($r)) 3657 return array($r); 3658 else 3659 return $r; 3660 } 3661 3662 3663 3664 /** 3665 * Get Writeable Users 3666 * 3667 * @return array An array listing all users 3668 * who have write access to this event 3669 * 3670 * @access public 3671 * 3672 */ 3673 function getWriteableUsers() 3674 { 3675 $w = $this->writeable_users->getValue(); 3676 if (empty($w)) 3677 return array(); 3678 else if (is_string($w)) 3679 return array($w); 3680 else 3681 return $w; 3682 } 3683 3684 3685 3686 /** 3687 * Get Unknown Attributes 3688 * 3689 * @return array An array of all unknown attributes 3690 * 3691 * @access public 3692 * 3693 */ 3694 function getUnknownAttributes() 3695 { 3696 return $this->unknownAttributes; 3697 } 3698 3699 3700 3701 /** 3702//LEFT OFF HERE -- after my massive rewrite (added/removed some fields, make sure this 3703//LEFT OFF HERE -- validation stuff is OK -- surely there are some changes! 3704//LEFT OFF HERE ------- make sure if this is a TODO that status is a TODO status constant 3705//LEFT OFF HERE ------- make sure if this is a EVENT that status is a EVENT status constant 3706 * Check Validity of All Event Fields 3707 * 3708 * Determines if the data contained in this object 3709 * is sufficient for saving the event correctly. 3710 * 3711 * @return string An error message if there is any invalid 3712 * field data, otherwise an empty string 3713 * is returned. 3714 * 3715 * @access public 3716 * 3717 */ 3718 function validateFields() 3719 { 3720 3721//LEFT OFF HERE -- these are all checks good for one time and recurring events, but NOT for tasks/todos or holidays! 3722//LEFT OFF HERE -- these are all checks good for one time and recurring events, but NOT for tasks/todos or holidays! 3723//LEFT OFF HERE -- these are all checks good for one time and recurring events, but NOT for tasks/todos or holidays! 3724//LEFT OFF HERE -- these are all checks good for one time and recurring events, but NOT for tasks/todos or holidays! 3725 $d = $this->getDomain(); 3726 if (empty($d)) 3727 return _("Must have domain information"); 3728 3729 if (!$this->isTask() 3730 && $this->getEventType() != SM_EVENT_TYPE_EVENT) 3731 return _("Valid event type must be specified"); 3732 3733 global $RECURRENCE_TYPES; 3734 if ($this->isRecurring() 3735 && !in_array($this->getRecurrenceType(), array_keys($RECURRENCE_TYPES))) 3736 return _("Must specify valid recurrence type"); 3737 3738 if ($this->isRecurring()) 3739 { 3740 $rrule = $this->getRecurrenceRule(); 3741 if (isset($rrule['UNTIL']) && !empty($rrule['UNTIL']) 3742 && $rrule['UNTIL'] < $this->getEndDateTime() 3743 - date('H', $this->getEndDateTime()) * 3600 3744 - date('i', $this->getEndDateTime()) * 60) 3745 return _("Overall end of recurrence for recurring events must come after end of initial occurrence"); 3746 } 3747 3748 if ($this->isRecurring() 3749 && (!is_numeric($this->getRecurrenceInterval()) || $this->getRecurrenceInterval() < 1)) 3750 return _("Must specify valid recurrence interval"); 3751 3752 $s = $this->getSummary(); 3753 if (empty($s)) 3754 return _("Event must have a summary/title"); 3755 3756 global $EVENT_PRIORITIES; 3757 if (!in_array($this->getPriority(), array_keys($EVENT_PRIORITIES))) 3758 return _("Must specify valid event priority"); 3759 3760 $st = $this->getStartDateTime(); 3761 if (empty($st) || !is_numeric($st)) 3762 return _("Event must have a valid start date/time"); 3763 3764 $et = $this->getEndDateTime(); 3765 if (empty($et) || !is_numeric($et)) 3766 return _("Event must have a valid end date/time"); 3767 3768 if ($et < $st) 3769 return _("End date/time must come after start date and time"); 3770 3771 $p = $this->getParentCalendars(); 3772 if (empty($p) || !is_array($p)) 3773 return _("At least one parent calendar must be given"); 3774 3775 $cb = $this->createdBy(); 3776 if (empty($cb)) 3777 return _("Event must have a creator"); 3778 3779 $co = $this->createdOn(); 3780 if (empty($co)) 3781 return _("Event must have a creation date"); 3782 3783 $o = $this->getOwners(); 3784 if (empty($o) || !is_array($o)) 3785 return _("Event must have at least one owner"); 3786 3787 3788 // no problems found 3789 // 3790 return ''; 3791 3792 } 3793 3794 3795 3796 /** 3797 * Determines if this event comes before 3798 * or after the given event, for use with 3799 * sorting events. 3800 * 3801 * @param object $otherEvent The event to compare 3802 * to this one. 3803 * 3804 * @return int -1 if this event comes first, 3805 * 1 if this event comes second, 3806 * or 0 if the events should be 3807 * considered to be "equal". 3808 * 3809 * @access public 3810 * 3811 */ 3812 function compareTo($otherEvent) 3813 { 3814 3815 // events of the same type? just compare start dates 3816 // 3817 if (($this->isOneTime() 3818 && $otherEvent->isOneTime()) 3819 || ($this->isRecurring() 3820 && $otherEvent->isRecurring())) 3821 { 3822 3823 if ($this->getStartDateTime() < $otherEvent->getStartDateTime()) 3824 return -1; 3825 3826 if ($this->getStartDateTime() > $otherEvent->getStartDateTime()) 3827 return 1; 3828 3829 return 0; 3830 3831 } 3832 3833 3834 // different types? we always sort recurring events first 3835 // 3836//TODO: hmmm, maybe we want to do something smarter here? 3837 else if ($this->isOneTime() 3838 && $otherEvent->isRecurring()) 3839 return 1; 3840 3841 3842 // different types? we always sort recurring events first 3843 // 3844//TODO: hmmm, maybe we want to do something smarter here? 3845 else if ($this->isRecurring() 3846 && $otherEvent->isOneTime()) 3847 return -1; 3848 3849 3850 else 3851 { 3852 global $color; 3853 plain_error_message('ERROR IN EVENT CLASS (compareTo): Cannot compare events without event type', $color); 3854 exit; 3855 } 3856 3857 } 3858 3859 3860 3861 /** 3862 * Returns the lenth of this event in number of 3863 * minutes 3864 * 3865 * @return int The number of minutes that this event lasts 3866 * 3867 * @access public 3868 * 3869 */ 3870 function lengthInMinutes() 3871 { 3872 3873 return (($this->getEndDateTime() - $this->getStartDateTime()) / 60); 3874 3875 } 3876 3877 3878 3879 /** 3880 * Returns the lenth of this event in number of 3881 * quarter hours 3882 * 3883 * @return int The number of quarter hours that 3884 * this event lasts 3885 * 3886 * @access public 3887 * 3888 */ 3889 function lengthInQuarterHours() 3890 { 3891 3892 // can't simply subtract start time from end time 3893 // because events that start, say at 1:59 3894 // and end at 2:16 will be shown as only 2 quarters 3895 // long, but for display purposes, the return value 3896 // has to be three in such a case 3897 // 3898 // so we extend start and end times to fill out 3899 // the quarter of the hour that they fall in first 3900 // and then do the math 3901 // 3902 $start = getdate($this->getStartDateTime()); 3903 $startMinute = $start['minutes']; 3904 if ($startMinute < 15) $startMinute = 0; 3905 else if ($startMinute < 30) $startMinute = 15; 3906 else if ($startMinute < 45) $startMinute = 30; 3907 else $startMinute = 45; 3908 $start = mktime($start['hours'], $startMinute, $start['seconds'], 3909 $start['mon'], $start['mday'], $start['year']); 3910 3911 $end = getdate($this->getEndDateTime()); 3912 $endMinute = $end['minutes']; 3913 $endHour = $end['hours']; 3914 if ($endMinute > 45) 3915 { 3916 $endMinute = 0; 3917 $endHour++; 3918 } 3919 else if ($endMinute > 30) $endMinute = 45; 3920 else if ($endMinute > 15) $endMinute = 30; 3921 else if ($endMinute > 0) $endMinute = 15; 3922 $end = mktime($endHour, $endMinute, $end['seconds'], 3923 $end['mon'], $end['mday'], $end['year']); 3924 3925 return round(($end - $start) / 60 / 15); 3926 3927 } 3928 3929 3930 3931 /** 3932 * Returns the quarter hour during which this 3933 * event starts (0, 15, 30 or 45). 3934 * 3935 * This method helps abstract the fact that 3936 * recurring events might have start timestamps 3937 * on a completely different day but should be 3938 * seen as starting on other days at a similar 3939 * time. 3940 * 3941 * Note that regular events that occur on the given 3942 * day but start on an earlier day are seen as starting 3943 * at midnight. 3944 * 3945 * @param int $year The year of the day for which 3946 * to check start time 3947 * @param int $month The month of the day for which 3948 * to check start time 3949 * @param int $day The day for which to check start time 3950 * 3951 * @return mixed The quarter of the hour during which 3952 * this event starts (0, 15, 30 or 45), 3953 * or FALSE if this event does not have 3954 * a start time on the given day. 3955 * 3956 * @access public 3957 * 3958 */ 3959 function startQuarterHour($year, $month, $day) 3960 { 3961 3962 if (!$this->occursOnDay($year, $month, $day)) return FALSE; 3963 3964 if (!$this->startsOnDay($year, $month, $day)) return 0; 3965 3966 $startMinute = date('i', $this->getStartDateTime()); 3967 if ($startMinute < 15) return 0; 3968 if ($startMinute < 30) return 15; 3969 if ($startMinute < 45) return 30; 3970 return 45; 3971 3972 } 3973 3974 3975 3976 /** 3977 * Returns the quarter hour during which this 3978 * event ends (0, 15, 30, 45 or 60 (meaning next 3979 * hour, zero minute)). 3980 * 3981 * This method helps abstract the fact that 3982 * recurring events might have end timestamps 3983 * on a completely different day but should be 3984 * seen as ending on other days at a similar 3985 * time. 3986 * 3987 * Note that regular events that occur on the given 3988 * day but end on a later day are seen as ending 3989 * at 23:45. 3990 * 3991 * @param int $year The year of the day for which 3992 * to check end time 3993 * @param int $month The month of the day for which 3994 * to check end time 3995 * @param int $day The day for which to check end time 3996 * 3997 * @return mixed The quarter of the hour during which 3998 * this event ends (0, 15, 30, 45, or 60), 3999 * or FALSE if this event does not have 4000 * a end time on the given day. 4001 * 4002 * @access public 4003 * 4004 */ 4005 function endQuarterHour($year, $month, $day) 4006 { 4007 4008 if (!$this->occursOnDay($year, $month, $day)) return FALSE; 4009 4010 if (!$this->endsOnDay($year, $month, $day)) return 45; 4011 4012 $endMinute = date('i', $this->getEndDateTime()); 4013 if ($endMinute > 45) return 60; 4014 if ($endMinute > 30) return 45; 4015 if ($endMinute > 15) return 30; 4016 if ($endMinute > 0) return 15; 4017 return 0; 4018 4019 } 4020 4021 4022 4023 /** 4024 * Returns start date (date only, no time) in 4025 * displayable format 4026 * 4027 * @param string $format The desired format of 4028 * the returned date (optional) 4029 * @param int $year For recurring events, the year that 4030 * identifies the desired occurrence for 4031 * which to return formatted start date 4032 * (optional; defaults to initial start 4033 * date) 4034 * @param int $month For recurring events, the month that 4035 * identifies the desired occurrence for 4036 * which to return formatted start date 4037 * (optional; defaults to initial start 4038 * date) 4039 * @param int $day For recurring events, the day that 4040 * identifies the desired occurrence for 4041 * which to return formatted start date 4042 * (optional; defaults to initial start 4043 * date) 4044 * 4045 * @return string The desired date 4046 * 4047 * @access public 4048 * 4049 */ 4050 function formattedStartDate($format='', $year=0, $month=0, $day=0) 4051 { 4052 4053 if (empty($format)) 4054 $format = 'M d, Y'; 4055 4056 4057 if ($year == 0 || $month == 0) // days could be zero or negative || $day == 0) 4058 return date_intl($format, $this->getStartDateTime()); 4059 4060 4061 // 4062 // otherwise need to check actual event occurrence 4063 // on the given day to determine this occurrence's 4064 // date 4065 // 4066 4067 4068 // regular one-time events are easy to figure out 4069 // 4070 if ($this->isOneTime()) 4071 { 4072 4073 return date_intl($format, $this->getStartDateTime()); 4074 4075 } 4076 4077 4078 // recurring events require more involved check 4079 // 4080 else if ($this->isRecurring()) 4081 { 4082 4083 $theDay = mktime(0, 0, 0, $month, $day, $year); 4084 4085 4086 // in case day is out of range, bring it back in 4087 // 4088 $correctedDate = getdate($theDay); 4089 $month = $correctedDate['mon']; 4090 $day = $correctedDate['mday']; 4091 $year = $correctedDate['year']; 4092 4093 4094 // original start date? same as above 4095 // 4096 if (dayIsBetween($year, $month, $day, $this->getStartDateTime(), $this->getEndDateTime())) 4097 return date_intl($format, $this->startDateTime); 4098 4099 4100 // otherwise, iterate through occurrences looking for match 4101 // 4102//LEFT OFF HERE --- the max date being passed in is midnight, but that's too early 4103 $occurrenceDate = $this->iterateEventOccurrences('findOccurrenceStartDate', 4104 array($year, $month, $day), 4105 $theDay); 4106 4107 if ($occurrenceDate === FALSE) 4108 { 4109 global $color; 4110 plain_error_message('ERROR IN EVENT CLASS (formattedStartDate): Event does not occur on given date', $color); 4111 exit; 4112 } 4113 4114 return date_intl($format, $occurrenceDate); 4115 4116 } 4117 4118 } 4119 4120 4121 4122 /** 4123 * Returns end date (date only, no time) in 4124 * displayable format 4125 * 4126 * @param string $format The desired format of 4127 * the returned date (optional) 4128 * @param int $year For recurring events, the year that 4129 * identifies the desired occurrence for 4130 * which to return formatted end date 4131 * (optional; defaults to initial end 4132 * date) 4133 * @param int $month For recurring events, the month that 4134 * identifies the desired occurrence for 4135 * which to return formatted end date 4136 * (optional; defaults to initial end 4137 * date) 4138 * @param int $day For recurring events, the day that 4139 * identifies the desired occurrence for 4140 * which to return formatted end date 4141 * (optional; defaults to initial end 4142 * date) 4143 * 4144 * @return string The desired date 4145 * 4146 * @access public 4147 * 4148 */ 4149 function formattedEndDate($format='', $year=0, $month=0, $day=0) 4150 { 4151 4152 if (empty($format)) 4153 $format = 'M d, Y'; 4154 4155 4156 if ($year == 0 || $month == 0) // days could be zero or negative || $day == 0) 4157 return date_intl($format, $this->getEndDateTime()); 4158 4159 4160 // 4161 // otherwise need to check actual event occurrence 4162 // on the given day to determine this occurrence's 4163 // date 4164 // 4165 4166 4167 // regular one-time events are easy to figure out 4168 // 4169 if ($this->isOneTime()) 4170 { 4171 4172 return date_intl($format, $this->getEndDateTime()); 4173 4174 } 4175 4176 4177 // recurring events require more involved check 4178 // 4179 else if ($this->isRecurring()) 4180 { 4181 4182 $theDay = mktime(0, 0, 0, $month, $day, $year); 4183 4184 4185 // in case day is out of range, bring it back in 4186 // 4187 $correctedDate = getdate($theDay); 4188 $month = $correctedDate['mon']; 4189 $day = $correctedDate['mday']; 4190 $year = $correctedDate['year']; 4191 4192 4193 // original start date? same as above 4194 // 4195 if (dayIsBetween($year, $month, $day, $this->getStartDateTime(), $this->getEndDateTime())) 4196 return date_intl($format, $this->getEndDateTime()); 4197 4198 4199 // otherwise, iterate through occurrences looking for match 4200 // 4201//LEFT OFF HERE --- the max date being passed in is midnight, but that's too early 4202 $occurrenceDate = $this->iterateEventOccurrences('findOccurrenceStartDate', 4203 array($year, $month, $day), 4204 $theDay); 4205 4206 if ($occurrenceDate === FALSE) 4207 { 4208 global $color; 4209 plain_error_message('ERROR IN EVENT CLASS (formattedEndDate): Event does not occur on given date', $color); 4210 exit; 4211 } 4212 4213 return date_intl($format, $occurrenceDate + $this->getEndDateTime() - $this->getStartDateTime()); 4214 4215 } 4216 4217 } 4218 4219 4220 4221 /** 4222 * Returns end recurrence date (if available) in displayable format 4223 * 4224 * @param string $format The desired format of 4225 * the returned date (optional) 4226 * 4227 * @return mixed The desired date as a string, or FALSE 4228 * if the recurrence rule does not exist 4229 * or does not contain an UNTIL clause 4230 * 4231 * @access public 4232 * 4233 */ 4234 function formattedEndRecurrenceDate($format='M d, Y') 4235 { 4236//TODO: when GUI has a way to indicate end of exclusion rule (yeah, right, 4237// when will that really be useful?), need a similar function for 4238// formattedEndRecurrenceExclusionDate 4239 4240 $rrule = $this->getRecurrenceRule(); 4241 4242 if (!empty($rrule) && isset($rrule['UNTIL']) && !empty($rrule['UNTIL'])) 4243 return date_intl($format, $rrule['UNTIL']); 4244 4245 return FALSE; 4246 4247 } 4248 4249 4250 4251 /** 4252 * Returns end recurrence count (if available) 4253 * 4254 * @return mixed The desired max recurrence count (integer), 4255 * or FALSE if the recurrence rule does not exist 4256 * or does not contain a COUNT clause 4257 * 4258 * @access public 4259 * 4260 */ 4261 function maxRecurrenceCount() 4262 { 4263//TODO: when GUI has a way to indicate end of exclusion rule (yeah, right, 4264// when will that really be useful?), need a similar function for 4265// maxRecurrenceExclusionCount 4266 4267 $rrule = $this->getRecurrenceRule(); 4268 4269 if (!empty($rrule) && isset($rrule['COUNT']) && !empty($rrule['COUNT'])) 4270 return $rrule['COUNT']; 4271 4272 return FALSE; 4273 4274 } 4275 4276 4277 4278 /** 4279 * Returns start time (time only, no date) 4280 * 4281 * @return array A three-element array, the first 4282 * element being the hours (24-hour 4283 * clock), the second being the minutes, 4284 * the third being the seconds. 4285 * 4286 * @access public 4287 * 4288 */ 4289 function startTime() 4290 { 4291 4292 $dateInfo = getdate($this->getStartDateTime()); 4293 return array($dateInfo['hours'], $dateInfo['minutes'], $dateInfo['seconds']); 4294 4295 } 4296 4297 4298 4299 /** 4300 * Returns end time (time only, no date) 4301 * 4302 * @return array A two-element array, the first 4303 * element being the hours (24-hour 4304 * clock), the second being the minutes, 4305 * the third being the seconds. 4306 * 4307 * @access public 4308 * 4309 */ 4310 function endTime() 4311 { 4312 4313 $dateInfo = getdate($this->getEndDateTime()); 4314 return array($dateInfo['hours'], $dateInfo['minutes'], $dateInfo['seconds']); 4315 4316 } 4317 4318 4319 4320 /** 4321 * Returns start time (time only, no date) in 4322 * displayable format 4323 * 4324 * @param string $format The desired format of 4325 * the returned time (optional) 4326 * @param int $twentyFourHourOverride This parameter may be used 4327 * to specify that time should 4328 * be returned in twenty-four 4329 * hour format (if given as 1), 4330 * twelve hour format (if given 4331 * as 0), or whatever the system 4332 * default is (when given as -1) 4333 * (an attempt will be made to 4334 * find a global configuration 4335 * variable to determine which 4336 * is desired) (optional; when 4337 * not specified, defaults to 4338 * system configuration 4339 * setting, or twenty-four hour 4340 * format if no global configuration 4341 * value is found). Note that this 4342 * parameter has no effect when 4343 * $format is given by the caller. 4344 * 4345 * @return string The desired time 4346 * 4347 * @access public 4348 * 4349 */ 4350 function formattedStartTime($format='', $twentyFourHourOverride=-1) 4351 { 4352 4353 if (empty($format)) 4354 { 4355 global $hour_format; 4356 if ($twentyFourHourOverride == -1) 4357 $twentyFourHourOverride = ($hour_format == SMPREF_TIME_24HR); 4358 4359 if ($twentyFourHourOverride == 0) 4360 $format = 'g:ia'; 4361 else 4362 $format = 'H:i'; 4363 } 4364 4365 4366 return date_intl($format, $this->getStartDateTime()); 4367 4368 } 4369 4370 4371 4372 /** 4373 * Returns end time (time only, no date) in 4374 * displayable format 4375 * 4376 * @param string $format The desired format of 4377 * the returned time (optional) 4378 * @param int $twentyFourHourOverride This parameter may be used 4379 * to specify that time should 4380 * be returned in twenty-four 4381 * hour format (if given as 1), 4382 * twelve hour format (if given 4383 * as 0), or whatever the system 4384 * default is (when given as -1) 4385 * (an attempt will be made to 4386 * find a global configuration 4387 * variable to determine which 4388 * is desired) (optional; when 4389 * not specified, defaults to 4390 * system configuration 4391 * setting, or twenty-four hour 4392 * format if no global configuration 4393 * value is found). Note that this 4394 * parameter has no effect when 4395 * $format is given by the caller. 4396 * 4397 * @return string The desired time 4398 * 4399 * @access public 4400 * 4401 */ 4402 function formattedEndTime($format='', $twentyFourHourOverride=-1) 4403 { 4404 4405 if (empty($format)) 4406 { 4407 global $hour_format; 4408 if ($twentyFourHourOverride == -1) 4409 $twentyFourHourOverride = ($hour_format == SMPREF_TIME_24HR); 4410 4411 if ($twentyFourHourOverride == 0) 4412 $format = 'g:ia'; 4413 else 4414 $format = 'H:i'; 4415 } 4416 4417 4418 return date_intl($format, $this->getEndDateTime()); 4419 4420 } 4421 4422 4423 4424 /** 4425 * Returns the year, month and day this event stops 4426 * recurring (if available) 4427 * 4428 * @return mixed If no recurrence rule is available, 4429 * or it does not have an UNTIL clause, 4430 * FALSE is returned; otherwise a three- 4431 * element array is returned, the first 4432 * element being the four-digit year, 4433 * the second being the month, and 4434 * the last being the day. 4435 * 4436 * @access public 4437 * 4438 */ 4439 function recurrenceEnd() 4440 { 4441 4442 $rrule = $this->getRecurrenceRule(); 4443 4444 if (!empty($rrule) && isset($rrule['UNTIL']) && !empty($rrule['UNTIL'])) 4445 { 4446 $dateInfo = getdate($rrule['UNTIL']); 4447 return array($dateInfo['year'], $dateInfo['mon'], $dateInfo['mday']); 4448 } 4449 4450 return FALSE; 4451 4452 } 4453 4454 4455 4456 /** 4457 * Returns the year, month and day this event starts 4458 * 4459 * @return array A three-element array, the first 4460 * element being the four-digit year, 4461 * the second being the month, and 4462 * the last being the day. 4463 * 4464 * @access public 4465 * 4466 */ 4467 function startDate() 4468 { 4469 4470 $dateInfo = getdate($this->getStartDateTime()); 4471 return array($dateInfo['year'], $dateInfo['mon'], $dateInfo['mday']); 4472 4473 } 4474 4475 4476 4477 /** 4478 * Returns the year, month and day this event ends 4479 * 4480 * @return array A three-element array, the first 4481 * element being the four-digit year, 4482 * the second being the month, and 4483 * the last being the day. 4484 * 4485 * @access public 4486 * 4487 */ 4488 function endDate() 4489 { 4490 4491 $dateInfo = getdate($this->getEndDateTime()); 4492 return array($dateInfo['year'], $dateInfo['mon'], $dateInfo['mday']); 4493 4494 } 4495 4496 4497 4498 /** 4499 * Returns the hour of day this event starts 4500 * on the given day (24-hour clock, no 4501 * leading zeros). 4502 * 4503 * This method helps abstract the fact that 4504 * recurring events might have start timestamps 4505 * on a completely different day but should be 4506 * seen as starting on other days at a similar 4507 * time. 4508 * 4509 * Note that regular events that occur on the given 4510 * day but start on an earlier day are seen as starting 4511 * at midnight. 4512 * 4513 * @param int $year The year of the day for which 4514 * to check start time 4515 * @param int $month The month of the day for which 4516 * to check start time 4517 * @param int $day The day for which to check start time 4518 * 4519 * @return mixed The hour of the day that this event 4520 * starts, or FALSE if this event does 4521 * not have a start time on the given day. 4522 * 4523 * @access public 4524 * 4525 */ 4526 function startHour($year, $month, $day) 4527 { 4528 4529 if (!$this->occursOnDay($year, $month, $day)) return FALSE; 4530 4531 if (!$this->startsOnDay($year, $month, $day)) return 0; 4532 4533 return date('G', $this->getStartDateTime()); 4534 4535 } 4536 4537 4538 4539 /** 4540 * Returns the hour of day this event ends 4541 * on the given day (24-hour clock, no 4542 * leading zeros). 4543 * 4544 * This method helps abstract the fact that 4545 * recurring events might have end timestamps 4546 * on a completely different day but should be 4547 * seen as ending on other days at a similar 4548 * time. 4549 * 4550 * Note that regular events that occur on the given 4551 * day but end on a later day are seen as ending 4552 * at 23:45. 4553 * 4554 * @param int $year The year of the day for which 4555 * to check end time 4556 * @param int $month The month of the day for which 4557 * to check end time 4558 * @param int $day The day for which to check end time 4559 * 4560 * @return mixed The hour of the day that this event 4561 * ends, or FALSE if this event does 4562 * not have a end time on the given day. 4563 * 4564 * @access public 4565 * 4566 */ 4567 function endHour($year, $month, $day) 4568 { 4569 4570 if (!$this->occursOnDay($year, $month, $day)) return FALSE; 4571 4572 if (!$this->endsOnDay($year, $month, $day)) return 23; 4573 4574 return date('G', $this->getEndDateTime()); 4575 4576 } 4577 4578 4579 4580 /** 4581 * Determines if this event starts between 4582 * the given timestamps. 4583 * 4584 * @param int $begin The beginning of the 4585 * timeframe to check for 4586 * event start. 4587 * @param int $end The end of the timeframe 4588 * to check for event start. 4589 * 4590 * @return boolean TRUE if this event starts 4591 * during the given timeframe, 4592 * FALSE otherwise. 4593 * 4594 * @access public 4595 * 4596 */ 4597 function startsBetween($begin, $end) 4598 { 4599echo "\n\n<hr><h3>Event->startsBetween(): coding not finished on this function! finish it before you use it somewhere!</h3><hr>";exit; 4600 4601 // regular one-time events are easy to figure out 4602 // 4603 if ($this->isOneTime()) 4604 { 4605 4606 return ($begin <= $this->getStartDateTime() && $this->getStartDateTime() <= $end); 4607 4608 } 4609 4610 4611 // recurring events require more involved check 4612 // 4613 else if ($this->isRecurring()) 4614 { 4615//LEFT OFF HERE 4616//LEFT OFF HERE 4617//LEFT OFF HERE 4618 } 4619 4620 4621 else 4622 { 4623 global $color; 4624 plain_error_message('ERROR IN EVENT CLASS (startsBetween): Cannot check event start time without event type', $color); 4625 exit; 4626 } 4627 4628 } 4629 4630 4631 4632 /** 4633 * Determines if this event has an occurrence on the 4634 * given date/time. 4635 * 4636 * @param int $timestamp The timestamp to check for occurrence 4637 * @param boolean $useExclusionRule When TRUE, recurring events 4638 * are evaluated using the 4639 * exclusion recurrence rule (optional; 4640 * default=FALSE) 4641 * 4642 * @return boolean TRUE if this event occurs on the given timestamp, FALSE otherwise. 4643 * 4644 * @access public 4645 * 4646 */ 4647 function occursOnTimestamp($timestamp, $useExclusionRule=FALSE) 4648 { 4649 4650 // regular one-time events are easy to figure out 4651 // 4652 if ($this->isOneTime()) 4653 { 4654 4655 return ($this->getStartDateTime() <= $timestamp 4656 && $timestamp <= $this->getEndDateTime()); 4657 4658 } 4659 4660 4661 // recurring events require more involved check 4662 // 4663 else if ($this->isRecurring()) 4664 { 4665 4666 // always occurs on the original start date 4667 // 4668 if ($this->getStartDateTime() <= $timestamp 4669 && $timestamp <= $this->getEndDateTime()) 4670 return TRUE; 4671 4672 4673 // otherwise, iterate through occurrences looking for match 4674 // 4675 return $this->iterateEventOccurrences('checkOccurrenceForTimestamp', 4676 array($timestamp), $timestamp, 4677 $useExclusionRule); 4678 4679 } 4680 4681 4682 else 4683 { 4684 global $color; 4685 plain_error_message('ERROR IN EVENT CLASS (occursOnTimestamp): Cannot check event occurrence without event type', $color); 4686 exit; 4687 } 4688 4689 } 4690 4691 4692 4693 /** 4694 * Determines if this event has an occurrence on the 4695 * given day. 4696 * 4697 * @param int $year The year of the day to check for occurrence 4698 * @param int $month The month of the day to check for occurrence 4699 * @param int $day The day to check for occurrence 4700 * 4701 * @return boolean TRUE if this event occurs on the given day, FALSE otherwise. 4702 * 4703 * @access public 4704 * 4705 */ 4706 function occursOnDay($year, $month, $day) 4707 { 4708 4709 // regular one-time events are easy to figure out 4710 // 4711 if ($this->isOneTime()) 4712 { 4713 4714 return dayIsBetween($year, $month, $day, $this->getStartDateTime(), $this->getEndDateTime()); 4715 4716 } 4717 4718 4719 // recurring events require more involved check 4720 // 4721 else if ($this->isRecurring()) 4722 { 4723 4724 $theDay = mktime(0, 0, 0, $month, $day, $year); 4725 4726 4727 // can we use date cache? this test doesn't test the contents 4728 // of $this->startDateCache directly, but trusts that it was built 4729 // correctly when the cachedOccurrences array was built 4730 // 4731 if (sizeof($this->cachedOccurrences) > 0 4732 && ($this->cachedOccurrences[sizeof($this->cachedOccurrences) - 1] >= $theDay 4733 || $this->cachedOccurrencesThruDate >= $theDay)) 4734 { 4735 return isset($this->startDateCache[$year][intval($month)][intval($day)]['timestamp']); 4736 } 4737 4738 4739 else 4740 { 4741 4742 // always occurs on the original start date 4743 // 4744 if (dayIsBetween($year, $month, $day, $this->getStartDateTime(), $this->getEndDateTime(), $theDay)) 4745 return TRUE; 4746 4747 4748 // otherwise, iterate through occurrences looking for match 4749 // 4750//LEFT OFF HERE --- the max date being passed in is midnight, but that's too early 4751 return $this->iterateEventOccurrences('checkOccurrence', 4752 array($year, $month, $day, $theDay), 4753 $theDay); 4754 4755 } 4756 4757 } 4758 4759 4760 else 4761 { 4762 global $color; 4763 plain_error_message('ERROR IN EVENT CLASS (occursOnDay): Cannot check event occurrence without event type', $color); 4764 exit; 4765 } 4766 4767 } 4768 4769 4770 4771 /** 4772 * Determines if this event starts on the given day. 4773 * 4774 * @param int $year The year of the day to check for starting. 4775 * @param int $month The month of the day to check for starting. 4776 * @param int $day The day to check for starting. 4777 * 4778 * @return boolean TRUE if this event starts on the given day, FALSE otherwise. 4779 * 4780 * @access public 4781 * 4782 */ 4783 function startsOnDay($year, $month, $day) 4784 { 4785 4786 $startInfo = getdate($this->getStartDateTime()); 4787 $theDay = mktime(0, 0, 0, $month, $day, $year); 4788 $dayInfo = getdate($theDay); 4789 4790 4791 // regular one-time events are easy to figure out 4792 // 4793 if ($this->isOneTime()) 4794 { 4795 4796 return ($startInfo['year'] == $year && $startInfo['yday'] == $dayInfo['yday']); 4797 4798 } 4799 4800 4801 // recurring events require more involved check 4802 // 4803 else if ($this->isRecurring()) 4804 { 4805 4806 // can we use date cache? this test doesn't test the contents 4807 // of $this->startDateCache directly, but trusts that it was built 4808 // correctly when the cachedOccurrences array was built 4809 // 4810 if (sizeof($this->cachedOccurrences) > 0 4811 && ($this->cachedOccurrences[sizeof($this->cachedOccurrences) - 1] >= $theDay 4812 || $this->cachedOccurrencesThruDate >= $theDay)) 4813 { 4814 return isset($this->startDateCache[$year][intval($month)][intval($day)]['timestamp']); 4815 } 4816 4817 4818 else 4819 { 4820 4821 // always starts on the original start date 4822 // 4823 if ($startInfo['year'] == $year && $startInfo['yday'] == $dayInfo['yday']) 4824 return TRUE; 4825 4826 4827 // otherwise, iterate through occurrences looking for match 4828 // 4829//LEFT OFF HERE --- the max date being passed in is midnight, but that's too early 4830 return $this->iterateEventOccurrences('checkStartsOnDay', 4831 array($year, $month, $day, $dayInfo), 4832 $theDay); 4833 4834 } 4835 4836 } 4837 4838 4839 else 4840 { 4841 global $color; 4842 plain_error_message('ERROR IN EVENT CLASS (startsOnDay): Cannot check event start without event type', $color); 4843 exit; 4844 } 4845 4846 } 4847 4848 4849 4850 /** 4851 * Determines if this event starts on the given date/time. 4852 * 4853 * @param int $timestamp The timestamp to check for starting. 4854 * @param boolean $useExclusionRule When TRUE, recurring events 4855 * are evaluated using the 4856 * exclusion recurrence rule (optional; 4857 * default=FALSE) 4858 * 4859 * @return boolean TRUE if this event starts on the given timestamp, FALSE otherwise. 4860 * 4861 * @access public 4862 * 4863 */ 4864 function startsOnTimestamp($timestamp, $useExclusionRule=FALSE) 4865 { 4866 4867 // regular one-time events are easy to figure out 4868 // 4869 if ($this->isOneTime()) 4870 { 4871 4872 return ($timestamp == $this->getStartDateTime()); 4873 4874 } 4875 4876 4877 // recurring events require more involved check 4878 // 4879 else if ($this->isRecurring()) 4880 { 4881 4882 // always starts on the original start date 4883 // 4884 if ($timestamp == $this->getStartDateTime()) 4885 return TRUE; 4886 4887 4888 // otherwise, iterate through occurrences looking for match 4889 // 4890 return $this->iterateEventOccurrences('checkStartsOnTimestamp', 4891 array($timestamp), $timestamp, $useExclusionRule); 4892 4893 } 4894 4895 4896 else 4897 { 4898 global $color; 4899 plain_error_message('ERROR IN EVENT CLASS (startsOnTimestamp): Cannot check event start without event type', $color); 4900 exit; 4901 } 4902 4903 } 4904 4905 4906 4907 /** 4908 * Determines if this event ends on the given day. 4909 * 4910 * @param int $year The year of the day to check for ending. 4911 * @param int $month The month of the day to check for ending. 4912 * @param int $day The day to check for ending. 4913 * 4914 * @return boolean TRUE if this event ends on the given day, FALSE otherwise. 4915 * 4916 * @access public 4917 * 4918 */ 4919 function endsOnDay($year, $month, $day) 4920 { 4921 4922 $endInfo = getdate($this->getEndDateTime()); 4923 $theDay = mktime(0, 0, 0, $month, $day, $year); 4924 $dayInfo = getdate($theDay); 4925 4926 4927 // regular one-time events are easy to figure out 4928 // 4929 if ($this->isOneTime()) 4930 { 4931 4932 return ($endInfo['year'] == $year && $endInfo['yday'] == $dayInfo['yday']); 4933 4934 } 4935 4936 4937 // recurring events require more involved check 4938 // 4939 else if ($this->isRecurring()) 4940 { 4941 4942 // can we use date cache? this test doesn't test the contents 4943 // of $this->endDateCache directly, but trusts that it was built 4944 // correctly when the cachedOccurrences array was built 4945 // 4946 if (sizeof($this->cachedOccurrences) > 0 4947 && ($this->cachedOccurrences[sizeof($this->cachedOccurrences) - 1] >= $theDay 4948 || $this->cachedOccurrencesThruDate >= $theDay)) 4949 { 4950 return isset($this->endDateCache[$year][intval($month)][intval($day)]['timestamp']); 4951 } 4952 4953 4954 else 4955 { 4956 4957 // always ends on the original end date 4958 // 4959 if ($endInfo['year'] == $year && $endInfo['yday'] == $dayInfo['yday']) 4960 return TRUE; 4961 4962 4963 // otherwise, iterate through occurrences looking for match 4964 // 4965//LEFT OFF HERE --- the max date being passed in is midnight, but that's too early 4966 return $this->iterateEventOccurrences('checkEndsOnDay', 4967 array($year, $month, $day, $dayInfo), 4968 $theDay); 4969 4970 } 4971 4972 } 4973 4974 4975 else 4976 { 4977 global $color; 4978 plain_error_message('ERROR IN EVENT CLASS (endsOnDay): Cannot check event end without event type', $color); 4979 exit; 4980 } 4981 4982 } 4983 4984 4985 4986 /** 4987 * Removes a user from this event, from the list 4988 * of owners, readable users, and writeable users 4989 * 4990 * @param string $user The user to be removed 4991 * 4992 * @access public 4993 * 4994 */ 4995 function remove_user($user) 4996 { 4997 4998 $this->owners->setValue(array_diff($this->getOwners(), array($user))); 4999 $this->readable_users->setValue(array_diff($this->getReadableUsers(), array($user))); 5000 $this->writeable_users->setValue(array_diff($this->getWriteableUsers(), array($user))); 5001 5002 } 5003 5004 5005 5006 /** 5007 * Adds a new user to this event 5008 * 5009 * @param string $user The user name being added 5010 * @param string $accessLevel The access level being 5011 * granted to the new user, 5012 * which should correspond 5013 * to the event access constants 5014 * defined in {@link constants.php} 5015 * @access public 5016 * 5017 */ 5018 function add_user($user, $accessLevel) 5019 { 5020 5021 if ($accessLevel == SM_CAL_ACCESS_LEVEL_OWNER) 5022 $this->owners->setValue(array_merge($this->getOwners(), array($user))); 5023 5024 else if ($accessLevel == SM_CAL_ACCESS_LEVEL_READ) 5025 $this->readable_users->setValue(array_merge($this->getReadableUsers(), array($user))); 5026 5027 else if ($accessLevel == SM_CAL_ACCESS_LEVEL_WRITE) 5028 $this->writeable_users->setValue(array_merge($this->getWriteableUsers(), array($user))); 5029 5030 } 5031 5032 5033 5034 /** 5035 * Determines if this event falls on the given calendar 5036 * 5037 * @param string $calID The ID of the calendar to check 5038 * 5039 * @return boolean TRUE if this event falls on the given calendar, FALSE otherwise 5040 * 5041 * @access public 5042 * 5043 */ 5044 function fallsOnCalendar($calID) 5045 { 5046 5047 return in_array($calID, $this->getParentCalendars()); 5048 5049 } 5050 5051 5052 5053 /** 5054 * Determines if the given user is an owner of this event 5055 * 5056 * @param string $user The user to inspect for ownership 5057 * 5058 * @return boolean TRUE if the user is an owner of this event, FALSE otherwise 5059 * 5060 * @access public 5061 * 5062 */ 5063 function isOwner($user) 5064 { 5065 5066 // can't just test to see if user is in owner array, since 5067 // we allow for wildcards in owner names 5068 // 5069 foreach ($this->getOwners() as $owner) 5070 if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'), 5071 strtoupper($owner)) . '$/', strtoupper($user))) 5072 return TRUE; 5073 5074 5075 return FALSE; 5076 5077 } 5078 5079 5080 5081 /** 5082 * Determines if the given user has read access to this event 5083 * 5084 * NOTE: please be aware that if a username returns FALSE for 5085 * this function, that user might still have read access if 5086 * they qualify via isOwner() or canWrite(). 5087 * 5088 * @param string $user The user to inspect for read permission 5089 * 5090 * @return boolean TRUE if the user has read access to this event, FALSE otherwise 5091 * 5092 * @access public 5093 * 5094 */ 5095 function canRead($user) 5096 { 5097 5098 // can't just test to see if user is in readable_users array, since 5099 // we allow for wildcards in user names 5100 // 5101 foreach ($this->getReadableUsers() as $readUser) 5102 if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'), 5103 strtoupper($readUser)) . '$/', strtoupper($user))) 5104 return TRUE; 5105 5106 5107 // also, events on public calendars should all be visible to anyone 5108 // 5109 foreach ($this->getParentCalendars() as $parentID) 5110 { 5111 $parent = get_calendar($parentID); 5112 if ($parent->getCalendarType() == SM_CAL_TYPE_PUBLIC) 5113 return TRUE; 5114 } 5115 5116 5117 return FALSE; 5118 5119 } 5120 5121 5122 5123 /** 5124 * Determines if the given user has write access to this event 5125 * 5126 * NOTE: please be aware that if a username returns FALSE for 5127 * this function, that user might still have write access if 5128 * they qualify via isOwner(). 5129 * 5130 * @param string $user The user to inspect for write permission 5131 * 5132 * @return boolean TRUE if the user has write access to this event, FALSE otherwise 5133 * 5134 * @access public 5135 * 5136 */ 5137 function canWrite($user) 5138 { 5139 5140 // can't just test to see if user is in writeable_users array, since 5141 // we allow for wildcards in user names 5142 // 5143 foreach ($this->getWriteableUsers() as $writeUser) 5144 if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'), 5145 strtoupper($writeUser)) . '$/', strtoupper($user))) 5146 return TRUE; 5147 5148 5149 return FALSE; 5150 5151 } 5152 5153 5154 5155 /** 5156 * Constructs iCal representation of this event 5157 * 5158 * Note that the returned text ONLY includes event 5159 * attributes, which, alone, is not, per RFC2445, 5160 * a valid iCal object. The caller should use the 5161 * output of this function inside of an iCal calendar 5162 * data stream. 5163 * 5164 * @param boolean $includeExtras When TRUE, all event 5165 * attributes are included 5166 * in the return value, even 5167 * those that are for internal 5168 * use only and should NOT be 5169 * part of output to the 5170 * outside world. When FALSE, 5171 * only RFC-allowable fields 5172 * are included (optional; 5173 * default = FALSE). 5174 * @param string $icalLineDelimOverride If given, will be used instead 5175 * the RFC-standard CRLF to terminate 5176 * iCal lines (optional) 5177 * 5178 * @return string The iCal stream representing this event 5179 * 5180 * @access public 5181 * 5182 */ 5183 function getICal($includeExtras=FALSE, $icalLineDelimOverride='') 5184 { 5185 5186 // figure out line delimiter 5187 // 5188 if (empty($icalLineDelimOverride)) 5189 $icalLineDelim = ICAL_LINE_DELIM; 5190 else 5191 $icalLineDelim = $icalLineDelimOverride; 5192 5193 5194 // start building iCal stream 5195 // 5196 $iCalText = 'BEGIN:' . $this->getEventType() . $icalLineDelim 5197 . $this->id->getICal($icalLineDelim) 5198 . $this->sequence->getICal($icalLineDelim) 5199 . $this->priority->getICal($icalLineDelim) 5200 . 'DTSTAMP:' . gmdate('Ymd\THis\Z', $this->thisObjectsTimestamp) . $icalLineDelim 5201 . $this->createdOn->getICal($icalLineDelim) 5202 . $this->lastUpdatedOn->getICal($icalLineDelim) 5203 . $this->status->getICal($icalLineDelim) 5204 . $this->startDateTime->getICal($icalLineDelim) 5205//LEFT OFF HERE 5206//TODO: following three properties should have language and encoding info! 5207 . $this->summary->getICal($icalLineDelim) 5208 . $this->description->getICal($icalLineDelim) 5209 . $this->comments->getICal($icalLineDelim); 5210 5211 5212 // duration or dtend/due? 5213 // 5214 $d = $this->getDuration(); 5215 if (!empty($d)) 5216 $iCalText .= $this->duration->getICal($icalLineDelim); 5217 else if ($this->isTask()) 5218 $iCalText .= $this->due->getICal($icalLineDelim); 5219 else 5220 $iCalText .= $this->endDateTime->getICal($icalLineDelim); 5221 5222 5223 // only for task/todos 5224 // 5225 if ($this->isTask()) 5226 $iCalText .= $this->percentComplete->getICal($icalLineDelim); 5227 5228 5229 // recurrence info 5230 // 5231 $rrule = $this->getRecurrenceRule(); 5232 $rdates = $this->getRecurrenceDates(); 5233 $exrule = $this->getRecurrenceExclusionRule(); 5234 $exdates = $this->getRecurrenceExclusionDates(); 5235 if (!empty($rrule)) 5236 $iCalText .= $this->recurrenceRule->getICal($icalLineDelim); 5237 if (!empty($exrule)) 5238 $iCalText .= $this->recurrenceExclusionRule->getICal($icalLineDelim); 5239 if (!empty($rdates)) 5240 $iCalText .= $this->recurrenceDates->getICal($icalLineDelim); 5241 if (!empty($exdates)) 5242 $iCalText .= $this->recurrenceExclusionDates->getICal($icalLineDelim); 5243 5244 5245//TODO: possible additions at some point... 5246// hmm, can we put this together from our internal type of the parent cal? "CLASS" 5247// CLASS:PUBLIC CLASS:PRIVATE CLASS:CONFIDENTIAL 5248// in the future: "ATTENDEE" 5249// in the future: "CATEGORIES" 5250// in the future: "CONTACT" 5251// maybe some day if we have "RELATED-TO" information... 5252// "TRANSP" if we can figure out the date stuff that requires this prop (anniversaries I think are required to be transparent, but that's regardless of this property anyway), but it's not that important (and/or have a user input for this setting) 5253 5254 5255 // unknown attributes are included, since they 5256 // were probably custom attributes defined by an 5257 // external source 5258 // 5259 foreach ($this->unknownAttributes as $attr) 5260 $iCalText .= $attr . $icalLineDelim; 5261 5262 5263 // include all our internal attributes? 5264 // 5265 if ($includeExtras) 5266 { 5267 $iCalText .= $this->dom->getICal($icalLineDelim) 5268 . $this->createdBy->getICal($icalLineDelim) 5269 . $this->lastUpdatedBy->getICal($icalLineDelim) 5270 . $this->owners->getICal($icalLineDelim) 5271 . $this->readable_users->getICal($icalLineDelim) 5272 . $this->writeable_users->getICal($icalLineDelim) 5273 . $this->parentCalendars->getICal($icalLineDelim); 5274 } 5275 5276 5277 $iCalText .= 'END:' . $this->getEventType() . $icalLineDelim; 5278 5279 5280 // fold lines that are too long 5281 // 5282 foldICalStreamByRef($iCalText); 5283 5284 5285 return $iCalText; 5286 5287 } 5288 5289 5290 5291 /** 5292 * Constructs an Event object from the given iCal stream 5293 * 5294 * @param array $iCalData The text value to be converted to an 5295 * Event object, one text line in 5296 * each array value. 5297 * 5298 * @return object An Event object representing the given iCal stream. 5299 * 5300 * @access public 5301 * 5302 */ 5303 function getEventFromICal($iCalText) 5304 { 5305 5306 // strip out CRLFs from each line 5307 // 5308 foreach ($iCalText as $x => $line) 5309 $iCalText[$x] = str_replace(ICAL_LINE_DELIM, '', $line); 5310 5311 5312 // unfold text 5313 // 5314 unfoldICalStreamByRef($iCalText); 5315 5316 5317 $id = ''; 5318 $sequence = ''; 5319 $type = ''; 5320 $summary = ''; 5321 $description = ''; 5322 $comments = ''; 5323 $status = ''; 5324 $priority = SM_CAL_EVENT_PRIORITY_NORMAL; 5325 $createdOn = ''; 5326 $lastUpdatedOn = ''; 5327 $startDateTime = ''; 5328 $endDateTime = ''; 5329 $due = ''; 5330 $duration = ''; 5331 $recurrenceRule = ''; 5332 $recurrenceDates = ''; 5333 $recurrenceExclusionRule = ''; 5334 $recurrenceExclusionDates = ''; 5335 $percentComplete = ''; 5336 5337 $dom = ''; 5338 $parentCalendars = ''; 5339 $owners = ''; 5340 $readable_users = ''; 5341 $writeable_users = ''; 5342 $unknownAttributes = array(); 5343 $createdBy = ''; 5344 $lastUpdatedBy = ''; 5345 5346 5347 // pull out properties 5348 // 5349 foreach ($iCalText as $line) 5350 { 5351 5352 $property = Property::extractICalProperty($line); 5353 5354 5355 // what do we do with each property? 5356 // 5357 switch ($property->getName()) 5358 { 5359 5360 // just skip these 5361 // 5362 case 'END': 5363 break; 5364 5365 5366 // get type -- VTODO or VEVENT? 5367 // 5368 case 'BEGIN': 5369 $type = $property; 5370 $type->setName('NO-ICAL-TYPE'); 5371 break; 5372 5373 5374 // event id 5375 // 5376 case 'UID': 5377 $id = $property; 5378 break; 5379 5380 5381 // sequence 5382 // 5383 case 'SEQUENCE': 5384 $sequence = $property; 5385 break; 5386 5387 5388 // summary 5389 // 5390 case 'SUMMARY': 5391 $summary = $property; 5392 break; 5393 5394 5395 // description 5396 // 5397 case 'DESCRIPTION': 5398 $description = $property; 5399 break; 5400 5401 5402 // comments 5403 // 5404 case 'COMMENT': 5405 $comments = $property; 5406 break; 5407 5408 5409 // status 5410 // 5411 case 'STATUS': 5412 $status = $property; 5413 break; 5414 5415 5416 // priority 5417 // 5418 case 'PRIORITY': 5419 $priority = $property; 5420 break; 5421 5422 5423 // date/time stamp 5424 // 5425 // at this time, there is no reason we need 5426 // to keep this information for ourselves 5427 // 5428 case 'DTSTAMP': 5429 // do nothing 5430 break; 5431 5432 5433 // creation date for events created with this application 5434 // (need to reparse the value as a date) 5435 // 5436 case 'CREATED': 5437 $createdOn = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5438 break; 5439 5440 5441 // date last modified for events created with this application 5442 // (need to reparse the value as a date) 5443 // 5444 case 'LAST-MODIFIED': 5445 $lastUpdatedOn = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5446 break; 5447 5448 5449 // start date 5450 // (need to reparse the value as a date) 5451 // 5452 case 'DTSTART': 5453 $startDateTime = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5454 break; 5455 5456 5457 // end date 5458 // (need to reparse the value as a date) 5459 // 5460 case 'DTEND': 5461 $endDateTime = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5462 break; 5463 5464 5465 // due 5466 // (need to reparse the value as a date) 5467 // 5468 case 'DUE': 5469 $due = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5470 break; 5471 5472 5473 // duration 5474 // 5475 case 'DURATION': 5476 $duration = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DURATION); 5477 break; 5478 5479 5480 // recurrence rule 5481 // 5482 case 'RRULE': 5483 $recurrenceRule = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_RRULE); 5484 break; 5485 5486 5487 // recurrence dates 5488 // (need to reparse the value as a date (or period)) 5489 // 5490 case 'RDATE': 5491 $recurrenceDates = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5492 break; 5493 5494 5495 // recurrence exclusion rule 5496 // 5497 case 'EXRULE': 5498 $recurrenceExclusionRule = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_RRULE); 5499 break; 5500 5501 5502 // recurrence exclusion dates 5503 // (need to reparse the value as a date) 5504 // 5505 case 'EXDATE': 5506 $recurrenceExclusionDates = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE); 5507 break; 5508 5509 5510 // percent complete 5511 // 5512 case 'PERCENT-COMPLETE': 5513 $percentComplete = $property; 5514 break; 5515 5516 5517 5518 // domain for events created with this application 5519 // 5520 case 'X-SQ-EVTDOMAIN': 5521 $dom = $property; 5522 break; 5523 5524 5525 // parent calendars for events created with this application 5526 // 5527 case 'X-SQ-EVTPARENTCALENDARS': 5528 $parentCalendars = $property; 5529 break; 5530 5531 5532 // event owners for events created with this application 5533 // 5534 case 'X-SQ-EVTOWNERS': 5535 $owners = $property; 5536 break; 5537 5538 5539 // event readable users for events created with this application 5540 // 5541 case 'X-SQ-EVTREADABLEUSERS': 5542 $readable_users = $property; 5543 break; 5544 5545 5546 // event writeable users for events created with this application 5547 // 5548 case 'X-SQ-EVTWRITEABLEUSERS': 5549 $writeable_users = $property; 5550 break; 5551 5552 5553 // user who created this event for events created with this application 5554 // 5555 case 'X-SQ-EVTCREATOR': 5556 $createdBy = $property; 5557 break; 5558 5559 5560 // user who last modified this event for events created with this application 5561 // 5562 case 'X-SQ-EVTLASTUPDATOR': 5563 $lastUpdatedBy = $property; 5564 break; 5565 5566 5567 // unknown parameters just pile into this array 5568 // 5569 default: 5570 $unknownAttributes[$property->getName()] = $line; 5571 break; 5572 5573 } 5574 5575 } 5576 5577 5578 return new Event($id, $sequence, $dom, $type, $summary, $description, 5579 $comments, $status, $priority, $startDateTime, $endDateTime, 5580 $due, $duration, $recurrenceRule, $recurrenceDates, 5581 $recurrenceExclusionRule, $recurrenceExclusionDates, 5582 $percentComplete, $parentCalendars, $createdBy, $createdOn, 5583 $lastUpdatedBy, $lastUpdatedOn, $owners, $readable_users, 5584 $writeable_users, $unknownAttributes); 5585 5586 5587 } 5588 5589 5590 5591} 5592 5593 5594 5595?> 5596