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