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  * Calendar class
13  *
14  */
15class Calendar
16{
17
18   var $id;
19   var $sequence;
20   var $dom;
21   var $prodID;
22   var $type;
23   var $name;
24   var $owners;
25   var $readable_users;
26   var $writeable_users;
27   var $createdOn;
28   var $lastUpdatedOn;
29   var $createdBy;
30   var $lastUpdatedBy;
31   var $holidays = array();
32   var $events;
33   var $allEvents;
34   var $unknownAttributes = array();
35
36   var $isExternalSource = FALSE;
37
38   var $currentMonthEventCache = 0;
39
40
41
42   /**
43     * Calendar constructor
44     *
45     * @param mixed $id The ID of this calendar (optional; ID is auto-
46     *                  generated if not given - NOTE that this is never
47     *                  a correct ID for (default) private type calendars!).
48     *                  May be specified as a string or a Property object.
49     * @param mixed $sequence The edit sequence ID for this calendar
50     *                        May be specified as an int or a Property object.
51     * @param mixed $dom The domain this calendar belongs under
52     *                   May be specified as a string or a Property object.
53     * @param mixed $prodID The product ID of the creating calendar system
54     *                       May be specified as a string or a Property object.
55     * @param mixed $type The type of this calendar, which should
56     *                    correspond to the calendar type constants
57     *                    defined in {@link constants.php}
58     *                    May be specified as a string or a Property object.
59     * @param mixed $name The name of this calendar
60     *                    May be specified as a string or a Property object.
61     * @param mixed $createdBy The name of the user who created this calendar
62     *                         May be specified as a string or a Property object.
63     * @param mixed $createdOn The date/time this calendar was created (optional;
64     *                         defaults to today's date)
65     *                         May be specified as a UTC-formatted timestamp
66     *                         string or a Property object.
67     * @param mixed $lastUpdatedBy The name of the user who last updated this calendar
68     *                             May be specified as a string or a Property object.
69     * @param mixed $lastUpdatedOn The date/time this calendar was last updated
70     *                             May be specified as a UTC-formatted timestamp
71     *                             string or a Property object.
72     * @param mixed $owners The users who share ownership of this calendar
73     *                      May be specified as an array or a Property object.
74     * @param mixed $readable_users The users who have read access to this calendar
75     *                              May be specified as an array or a Property object.
76     * @param mixed $writeable_users The users who have write access to this calendar
77     *                               May be specified as an array or a Property object.
78     * @param array $unknownAttributes Extra unknown attributes in an
79     *                                 array keyed by attribute name, although
80     *                                 the value MUST be the full iCal line
81     *                                 describing the property, INCLUDING its
82     *                                 name.  These properties are often
83     *                                 derived from custom attributes from an
84     *                                 imported iCal file
85     * @param string $fallbackName An additional name to be used if
86     *                             none is given above or in any of the
87     *                             calendar's extra attributes (optional)
88     * @param string $uploadFilename A flag that indicates the source of this
89     *                               calendar; if given, it represents the
90     *                               filename from which it was uploaded,
91     *                               in which case the ID (if not
92     *                               given) will be constructed differently,
93     *                               so that it is not create-time based.
94     *                               (optional; default = empty string)
95     *
96     * Note that by default, if no owner, readable or writeable user is
97     * specified, the current user is given read permission only.
98     *
99     * Note that by default, if no calendar type is specified, it is assumed
100     * to be a PUBLIC calendar!
101     *
102     */
103   function Calendar($id='', $sequence=0, $dom='', $prodID='', $type='', $name='',
104                     $createdBy='', $createdOn='', $lastUpdatedBy='', $lastUpdatedOn='',
105                     $owners=array(), $readable_users=array(), $writeable_users=array(),
106                     $unknownAttributes=array(), $fallbackName='', $uploadFilename='')
107   {
108
109      if (is_object($id) && strtolower(get_class($id)) == 'property')
110         $this->id                = $id;
111      else
112         $this->id                = new Property('X-SQ-CALID', $id);
113
114      if (is_object($sequence) && strtolower(get_class($sequence)) == 'property')
115         $this->sequence          = $sequence;
116      else
117         $this->sequence          = new Property('X-SQ-CALSEQUENCE', $sequence);
118
119      if (is_object($dom) && strtolower(get_class($dom)) == 'property')
120         $this->dom               = $dom;
121      else
122         $this->dom               = new Property('X-SQ-CALDOMAIN',
123                                    strtr($dom, '@|_-.:/ \\', '________'));
124
125      if (is_object($prodID) && strtolower(get_class($prodID)) == 'property')
126         $this->prodID            = $prodID;
127      else
128         $this->prodID            = new Property('PRODID', $prodID);
129
130      if (is_object($type) && strtolower(get_class($type)) == 'property')
131         $this->type              = $type;
132      else
133         $this->type              = new Property('X-SQ-CALTYPE', $type);
134
135      if (is_object($name) && strtolower(get_class($name)) == 'property')
136         $this->name              = $name;
137      else
138         $this->name              = new Property('X-SQ-CALNAME', $name);
139
140      if (is_object($createdBy) && strtolower(get_class($createdBy)) == 'property')
141         $this->createdBy         = $createdBy;
142      else
143         $this->createdBy         = new Property('X-SQ-CALCREATOR', $createdBy);
144
145      if (is_object($createdOn) && strtolower(get_class($createdOn)) == 'property')
146         $this->createdOn         = $createdOn;
147      else
148         $this->createdOn         = new Property('X-SQ-CALCREATED',
149                                 (empty($createdOn) ? gmdate('Ymd\THis\Z') : $createdOn),
150                                 array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC);
151
152      if (is_object($lastUpdatedBy) && strtolower(get_class($lastUpdatedBy)) == 'property')
153         $this->lastUpdatedBy     = $lastUpdatedBy;
154      else
155         $this->lastUpdatedBy     = new Property('X-SQ-CALLASTUPDATOR',
156                                 (empty($lastUpdatedBy) ? $this->createdBy() : $lastUpdatedBy));
157
158      if (is_object($lastUpdatedOn) && strtolower(get_class($lastUpdatedOn)) == 'property')
159         $this->lastUpdatedOn     = $lastUpdatedOn;
160      else
161         $this->lastUpdatedOn     = new Property('X-SQ-CALLAST-MODIFIED',
162                    (empty($lastUpdatedOn) ? $this->createdOn->getRawValue() : $lastUpdatedOn),
163                    array(), SM_CAL_ICAL_PROPERTY_TYPE_DATETIME_UTC);
164
165      if (is_object($owners) && strtolower(get_class($owners)) == 'property')
166         $this->owners            = $owners;
167      else
168         $this->owners            = new Property('X-SQ-CALOWNERS', $owners);
169
170      if (is_object($readable_users) && strtolower(get_class($readable_users)) == 'property')
171         $this->readable_users    = $readable_users;
172      else
173         $this->readable_users    = new Property('X-SQ-CALREADABLEUSERS', $readable_users);
174
175      if (is_object($writeable_users) && strtolower(get_class($writeable_users)) == 'property')
176         $this->writeable_users   = $writeable_users;
177      else
178         $this->writeable_users   = new Property('X-SQ-CALWRITEABLEUSERS', $writeable_users);
179
180      $this->unknownAttributes = $unknownAttributes;
181
182
183
184      $sm_cal_prodid = str_replace('###VERSION###', calendar_version(), SM_CAL_PRODID);
185      global $domain, $username, $useDomainInCalID;
186
187
188
189      // insert default values if not given above
190      //
191      // note that user is only given read access by default;
192      // caller must explicitly give any more than that
193      //
194      $o = $this->getOwners();
195      $w = $this->getWriteableUsers();
196      $r = $this->getReadableUsers();
197      if (empty($o) && empty($w) && empty($r))
198         $this->readable_users->setValue(array($username));
199
200      $c = $this->createdBy();
201      if (empty($c))
202      $this->setCreator($username);
203
204
205      $p = $this->getProductID();
206      if (empty($p))
207         $this->prodID->setValue($sm_cal_prodid);
208
209      $d = $this->dom->getValue();
210      if (empty($d))
211         $this->dom->setValue(strtr($domain, '@|_-.:/ \\', '________'));
212
213      $i = $this->getID();
214      if (empty($i))
215      {
216         //Note: Apple uses this field for cal ID: X-WR-RELCALID
217         if (!empty($unknownAttributes['X-WR-RELCALID']))
218         {
219            $this->id = Property::extractICalProperty($unknownAttributes['X-WR-RELCALID']);
220            unset($unknownAttributes['X-WR-RELCALID']);
221         }
222
223
224         // uploaded calendars get an ID that is constructed w/out
225         // the default of having the create time in it
226         //
227         else if ($uploadFilename)
228         {
229            // if calendar itself already has createdOn date,
230            // use that
231            //
232            if (is_object($createdOn) && strtolower(get_class($createdOn)) == 'property')
233               $this->id->setValue('sm_cal_' . gmdate('Ymd\THis\Z', $this->createdOn())
234                                 . ($useDomainInCalID ? '_' . $this->getDomain() : ''));
235
236
237            // otherwise, fudge it
238            //
239            else
240            {
241               // try to chop off extension/incrememtal file numbers
242               //
243               if (strlen($uploadFilename) > 6)
244                  $uploadFilename = substr($uploadFilename, 0, strlen($uploadFilename) - 5);
245               $this->id->setValue(strtr('sm_uploaded_cal_' . $uploadFilename . '__' . $username
246                                 . ($useDomainInCalID ? '__' . $domain : ''),
247                                   '@|_-.:/\ ', '________'));
248            }
249         }
250
251
252         else
253//Note: this is not a correct ID for (default) private cals!  caller should not
254//      rely on auto-ID generation for private calendars!
255            $this->id->setValue('sm_cal_' . gmdate('Ymd\THis\Z')
256                              . ($useDomainInCalID ? '_' . $this->getDomain() : ''));
257      }
258
259      $n = $this->getName();
260      if (empty($n))
261      {
262         //Note: Apple uses this field for cal name: X-WR-CALNAME
263         if (!empty($unknownAttributes['X-WR-CALNAME']))
264         {
265            $this->name = Property::extractICalProperty($unknownAttributes['X-WR-CALNAME']);
266            unset($unknownAttributes['X-WR-CALNAME']);
267         }
268         else if (!empty($fallbackName))
269            $this->name->setValue($fallbackName);
270         else
271//TODO: there has to be a better way to resolve a calendar name...!
272            $this->name->setValue($this->getProductID());
273      }
274
275//TODO: yikes, is this too permissive?  should we go the other way, assume private?
276//      although a user only has ONE private calendar, so that might be a bad idea
277      $t = $this->getCalendarType();
278      if (empty($t))
279         $this->type->setValue(SM_CAL_TYPE_PUBLIC);
280
281//sm_print_r($this);
282   }
283
284
285
286// ---------- PRIVATE ----------
287
288
289
290   /**
291     * Gathers calendar event data for the given month.
292     *
293     * NOTE that any events already in this object with
294     * the same event IDs as those being retrieved will
295     * be overwritten - the caller must save them first
296     * to avoid loss.
297     *
298     * @param int $year The year of the month to be retrieved
299     * @param int $month The month to be retrieved
300     * @param string $user The user for which events are being retrieved
301     *
302     * @access private
303     *
304     */
305   function retrieveEventsForMonth($year, $month, $user)
306   {
307
308      // if source is external, we already have everything
309      //
310      if ($this->isExternal()) return;
311
312
313      if (!is_array($this->events))
314         $this->events = array();
315
316
317      if (!isset($this->events['onetime'])
318       || !is_array($this->events['onetime']))
319         $this->events['onetime'] = array();
320
321
322      // get events from calendar backend
323      // and merge them with any other events
324      // that we might already have
325      //
326      $this->events['onetime']
327         = array_merge($this->events['onetime'],
328                       get_events_for_month($this->getID(), $year, $month, $user));
329
330   }
331
332
333
334   /**
335     * Gathers all calendar event, holiday and other data for
336     * all time periods.
337     *
338     * Most useful for exporting all events, as normal operation
339     * will be faster if only the needed events are in memory.
340     *
341     * In fact, events pulled in this function are stored in a
342     * separate array from what is normally used; the array
343     * is not categorized between one-time, recurring, holiday
344     * events, etc.
345     *
346     * NOTE that any events already in this object with
347     * the same event IDs as those being retrieved will
348     * be overwritten - the caller must save them first
349     * to avoid loss.
350     *
351     * @param string $user The user for which events are being retrieved
352     *
353     * @access private
354     *
355     */
356   function retrieveAllEvents($user)
357   {
358
359      // if source is external, we already have everything
360      //
361      if ($this->isExternal()) return;
362
363
364      if (!is_array($this->allEvents))
365         $this->allEvents = array();
366
367
368      // get events from calendar backend
369      // and merge them with any other events
370      // that we might already have
371      //
372      $this->allEvents = array_merge($this->allEvents, get_all_events($this->getID(), $user));
373
374   }
375
376
377
378   /**
379     * Gathers recurring calendar event data
380     *
381     * NOTE that any recurring events already in this object
382     * will be removed first - the caller must save them
383     * first to avoid loss.
384     *
385     * @param string $user The user for which events are being retrieved
386     *
387     * @access private
388     *
389     */
390   function retrieveRecurringEvents($user)
391   {
392
393      // if source is external, we already have everything
394      //
395      if ($this->isExternal()) return;
396
397
398      if (!is_array($this->events))
399         $this->events = array();
400
401
402      if (!isset($this->events['recurring'])
403       || !is_array($this->events['recurring']))
404         $this->events['recurring'] = array();
405
406
407      // get events from calendar backend
408      //
409      $this->events['recurring'] = get_recurring_events($this->getID(), $user);
410
411   }
412
413
414
415   /**
416     * Gathers calendar holiday data
417     *
418     * NOTE that any holidays already in this object
419     * will be removed first - the caller must save them
420     * first to avoid loss.
421     *
422     * @param string $user The user for which holidays are being retrieved
423     *
424     * @access private
425     *
426     */
427   function retrieveHolidays($user)
428   {
429
430      // if source is external, we already have everything
431      //
432      if ($this->isExternal()) return;
433
434
435      if (!is_array($this->holidays))
436         $this->holidays = array();
437
438
439      // get holidays from calendar backend
440      //
441      $this->holidays = get_calendar_holidays($this->getID(), $user);
442
443   }
444
445
446
447// ---------- PUBLIC ----------
448
449
450
451   /**
452     * Determines if the source of this calendar is externally located
453     *
454     * @return boolean TRUE if calendar source is an external URI, FALSE otherwise
455     *
456     * @access public
457     *
458     */
459   function isExternal()
460   {
461      return $this->isExternalSource;
462   }
463
464
465
466   /**
467     * Set As External
468     *
469     * Tells us if this calendar's source was from the outside world
470     *
471     * @param boolean $external TRUE if the source is external, FALSE otherwise
472     *
473     * @access public
474     *
475     */
476   function setExternal($external)
477   {
478      $this->isExternalSource = $external;
479   }
480
481
482
483   /**
484     * Get Calendar ID
485     *
486     * @return string This calendar's internal ID
487     *
488     * @access public
489     *
490     */
491   function getID()
492   {
493
494      // external calendars always have this prepended to the ID...
495      //
496      if ($this->isExternal())
497         return 'SM_EXTERNAL' . $this->id->getValue();
498      else
499         return $this->id->getValue();
500   }
501
502
503
504   /**
505     * Set Calendar ID
506     *
507     * @param string $id The new ID to be assigned to this calendar
508     *
509     * @access public
510     *
511     */
512   function setID($id)
513   {
514
515      // if already prefixed with external indicator, strip that off
516      //
517      if (strpos($id, 'SM_EXTERNAL') === 0)
518         $id = substr($id, 11);
519
520      $this->id->setValue($id);
521   }
522
523
524
525   /**
526     * Increment Sequence Number
527     *
528     * @access public
529     *
530     */
531   function incrementSequence()
532   {
533      $this->sequence->setValue($this->sequence->getValue() + 1);
534   }
535
536
537
538   /**
539     * Set Calendar Type
540     *
541     * @param string $type The new type to assign to this calendar,
542     *                     which should correspond to the calendar type
543     *                     constants defined in {@link constants.php}
544     *
545     * @access public
546     *
547     */
548   function setType($type)
549   {
550      $this->type->setValue($type);
551   }
552
553
554
555   /**
556     * Get Calendar Type
557     *
558     * @return string This calendar's type, which will
559     *                correspond to the calendar type
560     *                constants defined in {@link constants.php}
561     *
562     * @access public
563     *
564     */
565   function getCalendarType()
566   {
567      return $this->type->getValue();
568   }
569
570
571
572   /**
573     * Get Calendar Name
574     *
575     * @return string This calendar's name
576     *
577     * @access public
578     *
579     */
580   function getName()
581   {
582
583      // need to translate (default) personal calendar name
584      // here since we can't always count on the correct
585      // language having been used when it was created
586      //
587      global $username, $domain;
588      if ($this->getCalendarType() == SM_CAL_TYPE_PERSONAL
589       && get_personal_cal_id($username, $domain, TRUE) == $this->getID())
590      {
591         global $username;
592         return sprintf(_("Personal Calendar for %s"), $username);
593      }
594
595      return $this->name->getValue();
596   }
597
598
599
600   /**
601     * Set Calendar Name
602     *
603     * @param string $name The new name to be assigned to this calendar
604     *
605     * @access public
606     *
607     */
608   function setName($name)
609   {
610      $this->name->setValue($name);
611   }
612
613
614
615   /**
616     * Get Calendar Domain
617     *
618     * @return string This calendar's domain
619     *
620     * @access public
621     *
622     */
623   function getDomain()
624   {
625      return $this->dom->getValue();
626   }
627
628
629
630   /**
631     * Get Product ID
632     *
633     * @return string The product ID for the product that created this calendar
634     *
635     * @access public
636     *
637     */
638   function getProductID()
639   {
640      return $this->prodID->getValue();
641   }
642
643
644
645   /**
646     * Get Creator Name
647     *
648     * @return string This event's creator
649     *
650     * @access public
651     *
652     */
653   function createdBy()
654   {
655      return $this->createdBy->getValue();
656   }
657
658
659
660   /**
661     * Set Username of User Who Created This Calendar
662     *
663     * @param string $user The name of the user who created this calendar
664     *
665     * @access public
666     *
667     */
668   function setCreator($user)
669   {
670      return $this->createdBy->setValue($user);
671   }
672
673
674
675   /**
676     * Get Creation Date
677     *
678     * @return timestamp This calendar's creation date
679     *
680     * @access public
681     *
682     */
683   function createdOn()
684   {
685      return $this->createdOn->getValue();
686   }
687
688
689
690   /**
691     * Get User That Last Updated Calendar
692     *
693     * @return string This calendar's last editor
694     *
695     * @access public
696     *
697     */
698   function lastUpdatedBy()
699   {
700      return $this->lastUpdatedBy->getValue();
701   }
702
703
704
705   /**
706     * Set Username of User Who Last Updated This Calendar
707     *
708     * @param string $user The name of the user updating this calendar
709     *
710     * @access public
711     *
712     */
713   function setLastUpdator($user)
714   {
715      return $this->lastUpdatedBy->setValue($user);
716   }
717
718
719
720   /**
721     * Get Last Update Date
722     *
723     * @return timestamp The date of this calendar's last update
724     *
725     * @access public
726     *
727     */
728   function lastUpdatedOn()
729   {
730      return $this->lastUpdatedOn->getValue();
731   }
732
733
734
735   /**
736     * Set Last Update Date
737     *
738     * @param string $timestamp The date this calendar was last updated
739     *                          (should be a UTC-formatted date/time string)
740     *
741     * @access public
742     *
743     */
744   function setLastUpdateDate($timestamp)
745   {
746      return $this->lastUpdatedOn->setValue($timestamp);
747   }
748
749
750
751   /**
752     * Get Calendar Owners
753     *
754     * @return array An array listing all calendar owners
755     *
756     * @access public
757     *
758     */
759   function getOwners()
760   {
761      $o = $this->owners->getValue();
762      if (empty($o))
763         return array();
764      else if (is_string($o))
765         return array($o);
766      else
767         return $o;
768   }
769
770
771
772   /**
773     * Get Readable Users
774     *
775     * @return array An array listing all users
776     *               who have read access to this calendar
777     *
778     * @access public
779     *
780     */
781   function getReadableUsers()
782   {
783      $r = $this->readable_users->getValue();
784      if (empty($r))
785         return array();
786      else if (is_string($r))
787         return array($r);
788      else
789         return $r;
790   }
791
792
793
794   /**
795     * Get Writeable Users
796     *
797     * @return array An array listing all users
798     *               who have write access to this calendar
799     *
800     * @access public
801     *
802     */
803   function getWriteableUsers()
804   {
805      $w = $this->writeable_users->getValue();
806      if (empty($w))
807         return array();
808      else if (is_string($w))
809         return array($w);
810      else
811         return $w;
812   }
813
814
815
816   /**
817     * Get Unknown Attributes
818     *
819     * @return array An array of all unknown attributes
820     *
821     * @access public
822     *
823     */
824   function getUnknownAttributes()
825   {
826      return $this->unknownAttributes;
827   }
828
829
830
831   /**
832     * Add a series of new events to this calendar.  Events
833     * are merged with those already in the calendar.
834     *
835     * Note: can only accomodate up to 1,000,000 events in one
836     * calendar when $forceAddAll is TRUE.
837     *
838     * @param array $newEvents An array of event arrays, keyed
839     *                         by string sindicating event
840     *                         type ("onetime", "recurring", etc)
841     * @param string $oldParentCalID The ID of the calendar that
842     *                               has owned the events up to now
843     * @param boolean $holdNewEvents If TRUE, will NOT save new events
844     *                               (updated events are ALWAYS saved)
845     *                               to the backend, and will instead
846     *                               just keep the events in memory
847     *                               inside of the calendar object for
848     *                               saving at a later time. (optional;
849     *                               default = FALSE)
850     * @param boolean $forceAddAll If TRUE, will add all events,
851     *                             even when event IDs conflict
852     *                             (when events would otherwise be
853     *                             synched) (conflicts are avoided
854     *                             by adding random numbers to event
855     *                             IDs) (optional; default = FALSE)
856     *
857     * @access public
858     *
859     */
860   function addEvents($newEvents, $oldParentCalID,
861                      $holdNewEvents=FALSE, $forceAddAll=FALSE)
862   {
863
864      if (empty($newEvents) || !is_array($newEvents)) return;
865
866
867      if (!isset($this->events)
868       || !is_array($this->events))
869         $this->events = array();
870      if (!isset($this->events['onetime'])
871       || !is_array($this->events['onetime']))
872         $this->events['onetime'] = array();
873      if (!isset($this->events['recurring'])
874       || !is_array($this->events['recurring']))
875         $this->events['recurring'] = array();
876
877
878      $allEvents = array();
879//TODO: what about other event types???  todos? holidays?
880      if (isset($newEvents['onetime']) && is_array($newEvents['onetime']))
881         $allEvents = array_merge($allEvents, $newEvents['onetime']);
882
883      if (isset($newEvents['recurring']) && is_array($newEvents['recurring']))
884         $allEvents = array_merge($allEvents, $newEvents['recurring']);
885
886
887      //
888      //
889      foreach ($allEvents as $event)
890      {
891
892         // attempt to get same event from backend
893         //
894         $event2 = get_event($this->getID(), $event->getID());
895         if ($event2 === FALSE) $event2 = $this->getEvent($event->getID());
896
897
898         // if we have a duplicate event ID but are forced
899         // to add it anyway, create unique ID and then add it
900         //
901         if ($forceAddAll)
902         {
903            sq_mt_randomize();
904            $id = $event->getID();
905            while ($event2 !== FALSE)
906            {
907               $id = $event->getID() . mt_rand(1, 1000000);
908               $event2 = get_event($this->getID(), $id);
909               if ($event2 === FALSE) $event2 = $this->getEvent($id);
910            }
911            $event->setID($id);
912
913         }
914
915
916         // add event to this cal if not found
917         //
918         if ($event2 === FALSE)
919         {
920
921            $event->removeParentCalendar($oldParentCalID);
922            $event->addParentCalendar($this->getID());
923//TODO: see notes below about permissions not getting preserved
924            $event->resetPermissionsToParent(array($this));
925            $event->removeParentCalendar($oldParentCalID);
926
927
928            // save event in correct place in memory
929            // if we are not saving to the backend
930            //
931            if ($holdNewEvents)
932            {
933//TODO: what about other event types???  todos? holidays?
934               if ($event->isRecurring())
935                  $this->events['recurring'][] = $event;
936               else if ($event->isOneTime())
937                  $this->events['onetime'][] = $event;
938            }
939            else
940               create_event($this->getID(), $event);
941
942         }
943
944
945         // synch: if date of new event is newer,
946         // replace event, otherwise, skip it
947         //
948         else if ($event->lastUpdatedOn() > $event2->lastUpdatedOn())
949         {
950            $event->removeParentCalendar($oldParentCalID);
951            $event->addParentCalendar($this->getID());
952//TODO: uploaded events may at some point have different permissions
953//      that we want to obey, but for now, there should not be a
954//      difference between cal and evt perms.  and I am too lazy to
955//      check if an upload will somehow munge any perms we may have
956//      had (because it gets uploaded to a temporary new calendar
957//      that is different from this one)
958            $event->resetPermissionsToParent(array($this));
959            update_event($this->getID(), $event);
960         }
961
962      }
963
964   }
965
966
967
968   /**
969     * Dump all events currently cached in this object.  Note
970     * that this may not in all cases correspond to ALL the
971     * events on en entire calendar (although it will in the
972     * case of an uploaded/external calendar or after calling
973     * $this->retrieveAllEvents()).
974     *
975     * @return array An array of event arrays, keyed by strings
976     *               indicating event type ("onetime",
977     *               "recurring", etc)
978     *
979     * @access public
980     *
981     */
982   function getEvents()
983   {
984      return $this->events;
985   }
986
987
988
989   /**
990     * Get Event
991     *
992     * Looks for (and returns) an event amongst whatever events
993     * are currently loaded into this calendar.  FALSE is returned
994     * when the event is not found -- note that sometimes this may
995     * not mean that there is no such event in this calendar!
996     *
997     * @param string $eventID The ID of the event to search for
998     *
999     * @return object An event object if the event was found
1000     *                in memory in the current object; FALSE otherwise
1001     *
1002     * @access public
1003     *
1004     */
1005   function getEvent($eventID)
1006   {
1007//TODO: what about other event types???  todos? holidays?
1008
1009      // look amongst one time events
1010      //
1011      foreach ($this->events['onetime'] as $event)
1012         if ($event->getID() == $eventID)
1013            return $event;
1014
1015
1016      // look amongst recurring events
1017      //
1018      foreach ($this->events['recurring'] as $event)
1019         if ($event->getID() == $eventID)
1020            return $event;
1021
1022
1023      return FALSE;
1024   }
1025
1026
1027
1028   /**
1029     * Determines if this calendar comes before
1030     * or after the given calendar, for use with
1031     * sorting calendars.
1032     *
1033     * @param object $otherCalendar The calendar to
1034     *                              compare to this
1035     *                              one.
1036     *
1037     * @return int -1 if this calendar comes first,
1038     *             1 if this calendar comes second,
1039     *             or 0 if the calendars should be
1040     *             considered to be "equal".
1041     *
1042     * @access public
1043     *
1044     */
1045   function compareTo($otherCalendar)
1046   {
1047
1048      return strcasecmp($this->getName(), $otherCalendar->getName());
1049
1050   }
1051
1052
1053
1054   /**
1055     * Returns any events that occur on the given day
1056     *
1057     * @param int $year The year of the day whose events are being returned
1058     * @param int $month The month of the day whose events are being returned
1059     * @param int $day The day whose events are being returned
1060     * @param string $user The user for which events are being returned
1061     *
1062     * @return array A list of all the events that occur today, sorted
1063     *               chronologically (array of Event objects)
1064     *
1065     * @access public
1066     *
1067     */
1068   function getEventsForDay($year, $month, $day, $user)
1069   {
1070
1071      // we only cache one month's worth of events at once
1072      //
1073      if (!$this->isExternal() && $this->currentMonthEventCache != $month)
1074         unset($this->events['onetime']);
1075
1076
1077      // get events if needed
1078      //
1079      if (!isset($this->events['onetime'])
1080       || !is_array($this->events['onetime']))
1081         $this->retrieveEventsForMonth($year, $month, $user);
1082      if (!isset($this->events['recurring'])
1083       || !is_array($this->events['recurring']))
1084      {
1085         $this->retrieveRecurringEvents($user);
1086
1087         // force events to cache all recurrences, which speeds
1088         // responsiveness tremendously
1089         //
1090         // Cache thru the 7th of January, two years ahead,
1091         // because the most extreme case is year view where
1092         // we start by getting the last few days of the previous
1093         // year.  For month and day view, this is quite a bit
1094         // of overkill; I don't think it is much of a hit, but
1095         // it *could* be, especially with many complicated RRULEs.
1096         // So if needed, it might be possible to make a
1097         // differentiation for that here and only cache as
1098         // much as is really needed.
1099         //
1100         $cacheDate = mktime(12, 0, 0, 1, 7, $year + 2);
1101         foreach ($this->events['recurring'] as $index => $event)
1102            $this->events['recurring'][$index]->forceEventOccurrenceCache($cacheDate);
1103      }
1104
1105
1106
1107
1108      // loop through all events, looking for ones for this day
1109      //
1110      $returnArray = array();
1111      foreach ($this->events['onetime'] as $event)
1112         if ($event->occursOnDay($year, $month, $day))
1113            $returnArray[] = $event;
1114      foreach ($this->events['recurring'] as $event)
1115         if ($event->occursOnDay($year, $month, $day))
1116            $returnArray[] = $event;
1117
1118
1119      // resort return array by starting event time
1120      //
1121      usort($returnArray, 'sortEventsByTime');
1122
1123
1124      $this->currentMonthEventCache = $month;
1125
1126      return $returnArray;
1127
1128   }
1129
1130
1131
1132   /**
1133     * Displays the requested month
1134     *
1135     * @param int $year The year of the month to be shown
1136     * @param int $month The month to be shown
1137     * @param int $startDay The day of the week for first display column
1138     * @param string $user The user for which events are being displayed
1139     * @param string $viewType The type of month view to be shown, which
1140     *                         should correspond to the MONTH view mode
1141     *                         constants defined in {@link constants.php}
1142     *                         (optional; default is normal month view mode)
1143     *
1144     * @access public
1145     *
1146     */
1147   function showMonth($year, $month, $startDay, $user, $viewType=SM_CAL_VIEW_MODE_MONTH)
1148   {
1149
1150      // display events
1151      //
1152      if ($viewType == SM_CAL_VIEW_MODE_MONTH || $viewType == SM_CAL_VIEW_MODE_ALL_MONTHS
1153       || $viewType == SM_CAL_VIEW_MODE_MONTH_MINIATURE)
1154      {
1155         include_once(SM_PATH . 'plugins/calendar/interface/month.php');
1156         showCalendarMonth($this, $year, $month, $startDay, $user, $viewType);
1157      }
1158      else
1159      {
1160         global $color;
1161         plain_error_message('ERROR IN CALENDAR CLASS (showMonth): Invalid view mode', $color);
1162         exit;
1163      }
1164
1165   }
1166
1167
1168
1169   /**
1170     * Displays the requested year
1171     *
1172     * @param int $year The year to be shown
1173     * @param string $user The user for which events are being displayed
1174     *
1175     * @access public
1176     *
1177     */
1178   function showYear($year, $user)
1179   {
1180
1181//LEFT OFF HERE
1182//LEFT OFF HERE
1183
1184   }
1185
1186
1187
1188   /**
1189     * Displays all months for the requested year
1190     *
1191     * @param int $year The year to be shown
1192     * @param string $user The user for which events are being displayed
1193     * @param int $startDay The day of the week for first display column
1194     *
1195     * @access public
1196     *
1197     */
1198   function showAllMonths($year, $user, $startDay)
1199   {
1200
1201      for ($i = 1; $i < 13; $i++)
1202      {
1203         $this->showMonth($year, $i, $startDay, $user, SM_CAL_VIEW_MODE_ALL_MONTHS);
1204      }
1205
1206   }
1207
1208
1209
1210   /**
1211     * Displays the requested day
1212     *
1213     * @param int $year The year of the day to be shown
1214     * @param int $month The month of the day to be shown
1215     * @param int $day The day to be shown
1216     * @param string $user The user for which events are being displayed
1217     * @param boolean $allDay Indicates if the whole day should be
1218     *                        displayed, from midnight to midnight.  If
1219     *                        FALSE, a limited number of hours are
1220     *                        displayed per the configuration file.
1221     *
1222     * @access public
1223     *
1224     */
1225   function showDay($year, $month, $day, $user, $allDay=FALSE)
1226   {
1227
1228      // display events
1229      //
1230      include_once(SM_PATH . 'plugins/calendar/interface/day.php');
1231      showCalendarDay($this, $year, $month, $day, $user, $allDay);
1232
1233   }
1234
1235
1236
1237   /**
1238     * Returns any holidays that occur on the given day
1239     *
1240     * @param int $year The year of the day whose holidays are being returned
1241     * @param int $month The month of the day whose holidays are being returned
1242     * @param int $day The day whose holidays are being returned
1243     * @param string $user The user for which holidays are being returned
1244     *
1245     * @return array A list of all the holidays that occur today, sorted
1246     *               chronologically (array of Event objects)
1247     *
1248     * @access public
1249     *
1250     */
1251   function getHolidaysForDay($year, $month, $day, $user)
1252   {
1253
1254      // do we already have holidays in memory?
1255      // then we don't need to load them from the backend
1256      //
1257      if (empty($this->holidays) || !is_array($this->holidays) || sizeof($this->holidays) == 0)
1258      {
1259         $this->retrieveHolidays($user);
1260
1261         // force events to cache all recurrences, which speeds
1262         // responsiveness tremendously
1263         //
1264         // Cache thru the 7th of January, two years ahead,
1265         // because the most extreme case is year view where
1266         // we start by getting the last few days of the previous
1267         // year.  For month and day view, this is quite a bit
1268         // of overkill; I don't think it is much of a hit, but
1269         // it *could* be, especially with many complicated RRULEs.
1270         // So if needed, it might be possible to make a
1271         // differentiation for that here and only cache as
1272         // much as is really needed.
1273         //
1274         $cacheDate = mktime(12, 0, 0, 1, 7, $year + 2);
1275         foreach ($this->holidays as $index => $holiday)
1276            $this->holidays[$index]->forceEventOccurrenceCache($cacheDate);
1277
1278      }
1279
1280
1281      // loop through all holidays, looking for ones for this day
1282      //
1283      $returnArray = array();
1284      foreach ($this->holidays as $holiday)
1285         if ($holiday->occursOnDay($year, $month, $day))
1286            $returnArray[] = $holiday;
1287
1288
1289      return $returnArray;
1290
1291   }
1292
1293
1294
1295   /**
1296     * Removes a user from this calendar, from the list
1297     * of owners, readable users, and writeable users
1298     *
1299     * @param string $user The user to be removed
1300     *
1301     * @access public
1302     *
1303     */
1304   function remove_user($user)
1305   {
1306
1307      $this->owners->setValue(array_diff($this->getOwners(), array($user)));
1308      $this->readable_users->setValue(array_diff($this->getReadableUsers(), array($user)));
1309      $this->writeable_users->setValue(array_diff($this->getWriteableUsers(), array($user)));
1310
1311   }
1312
1313
1314
1315   /**
1316     * Adds a new user to this calendar
1317     *
1318     * @param string $user The user name being added
1319     * @param string $accessLevel The access level being
1320     *                            granted to the new user,
1321     *                            which should correspond
1322     *                            to the calendar access constants
1323     *                            defined in {@link constants.php}
1324     * @access public
1325     *
1326     */
1327   function add_user($user, $accessLevel)
1328   {
1329
1330      if ($accessLevel == SM_CAL_ACCESS_LEVEL_OWNER)
1331         $this->owners->setValue(array_merge($this->getOwners(), array($user)));
1332
1333      else if ($accessLevel == SM_CAL_ACCESS_LEVEL_READ)
1334         $this->readable_users->setValue(array_merge($this->getReadableUsers(), array($user)));
1335
1336      else if ($accessLevel == SM_CAL_ACCESS_LEVEL_WRITE)
1337         $this->writeable_users->setValue(array_merge($this->getWriteableUsers(), array($user)));
1338
1339   }
1340
1341
1342
1343   /**
1344     * Determines if the given user is an owner of this calendar
1345     *
1346     * @param string $user The user to inspect for ownership
1347     *
1348     * @return boolean TRUE if the user is an owner of this calendar, FALSE otherwise
1349     *
1350     * @access public
1351     *
1352     */
1353   function isOwner($user)
1354   {
1355
1356      // can't just test to see if user is in owner array, since
1357      // we allow for wildcards in owner names
1358      //
1359      foreach ($this->getOwners() as $owner)
1360         if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'),
1361                    strtoupper($owner)) . '$/', strtoupper($user)))
1362            return TRUE;
1363
1364
1365      return FALSE;
1366
1367   }
1368
1369
1370
1371   /**
1372     * Determines if the given user has read access to this calendar
1373     *
1374     * @param string $user The user to inspect for read permission
1375     *
1376     * @return boolean TRUE if the user has read access to this calendar, FALSE otherwise
1377     *
1378     * @access public
1379     *
1380     */
1381   function canRead($user)
1382   {
1383
1384      // public calendars are always readable to anyone
1385      // (TODO: in the future, we may want to limit this
1386      // per domain)
1387      //
1388      if ($this->getCalendarType() == SM_CAL_TYPE_PUBLIC)
1389         return TRUE;
1390
1391
1392      // can't just test to see if user is in readable_users array, since
1393      // we allow for wildcards in user names
1394      //
1395      foreach ($this->getReadableUsers() as $readUser)
1396         if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'),
1397                    strtoupper($readUser)) . '$/', strtoupper($user)))
1398            return TRUE;
1399
1400
1401      return FALSE;
1402
1403   }
1404
1405
1406
1407   /**
1408     * Determines if the given user has write access to this calendar
1409     *
1410     * @param string $user The user to inspect for write permission
1411     *
1412     * @return boolean TRUE if the user has write access to this calendar, FALSE otherwise
1413     *
1414     * @access public
1415     *
1416     */
1417   function canWrite($user)
1418   {
1419
1420      // can't just test to see if user is in writeable_users array, since
1421      // we allow for wildcards in user names
1422      //
1423      foreach ($this->getWriteableUsers() as $writeUser)
1424         if (preg_match('/^' . str_replace(array('?', '*'), array('\w{1}', '.*?'),
1425                    strtoupper($writeUser)) . '$/', strtoupper($user)))
1426            return TRUE;
1427
1428
1429      return FALSE;
1430
1431   }
1432
1433
1434
1435   /**
1436     * Save Events To Backend
1437     *
1438     * Saves all currently loaded events into the backend (NOTE
1439     * that this may not always represent ALL events for the
1440     * calendar!).  This should only be called once; events are
1441     * CREATED, and if you attempt to create them twice, errors
1442     * will be thrown.
1443     *
1444     * @param boolean $resetPermissions If TRUE, all events will
1445     *                                  have their permissions
1446     *                                  wiped and reset to match
1447     *                                  their parent calendar.
1448     *
1449     * @access public
1450     *
1451     */
1452   function saveEvents($resetPermissions)
1453   {
1454
1455      assert(is_bool($resetPermissions));
1456
1457
1458      // save one time events
1459      //
1460      foreach ($this->events['onetime'] as $event)
1461      {
1462         if ($resetPermissions)
1463            $event->resetPermissionsToParent();
1464
1465         create_event($this->getID(), $event);
1466      }
1467
1468      // save recurring events
1469      //
1470      foreach ($this->events['recurring'] as $event)
1471      {
1472         if ($resetPermissions)
1473            $event->resetPermissionsToParent();
1474
1475         create_event($this->getID(), $event);
1476      }
1477
1478//TODO: holidays?  todos?
1479
1480   }
1481
1482
1483
1484   /**
1485     * Constructs iCal representation of this calendar
1486     *
1487     * Note that the returned text ONLY includes calendar
1488     * attributes, which is not, per RFC2445, a valid iCal
1489     * object.  The caller should either insert at least
1490     * one other iCal component (event, timezone, etc)
1491     * or use the $includeEventsForUser parameter herein.
1492     *
1493     * @param boolean $includeExtras When TRUE, all calendar
1494     *                               attributes are included
1495     *                               in the return value, even
1496     *                               those that are for internal
1497     *                               use only and should NOT be
1498     *                               part of output to the
1499     *                               outside world.  When FALSE,
1500     *                               only RFC-allowable fields
1501     *                               are included (optional;
1502     *                               default = FALSE).
1503     * @param string $includeEventsForUser When non-empty, this should
1504     *                                     specify a user for which all
1505     *                                     events, holidays, and others
1506     *                                     are included (only events that
1507     *                                     the specified user has at least
1508     *                                     read access to are included).
1509     *                                     When not given, no events are
1510     *                                     included; only the calendar itself
1511     *                                     is returned (optional; default = empty).
1512     * @param string $icalLineDelimOverride If given, will be used instead
1513     *                                      the RFC-standard CRLF to terminate
1514     *                                      iCal lines (optional)
1515     *
1516     * @return string The iCal stream representing this calendar
1517     *
1518     * @access public
1519     *
1520     */
1521   function getICal($includeExtras=FALSE, $includeEventsForUser='',
1522                    $icalLineDelimOverride='')
1523   {
1524
1525      // figure out line delimiter
1526      //
1527      if (empty($icalLineDelimOverride))
1528         $icalLineDelim = ICAL_LINE_DELIM;
1529      else
1530         $icalLineDelim = $icalLineDelimOverride;
1531
1532
1533      $iCalVersion = '2.0';
1534      $sm_cal_prodid = str_replace('###VERSION###', calendar_version(), SM_CAL_PRODID);
1535
1536
1537      $iCalText = 'BEGIN:VCALENDAR' . $icalLineDelim
1538                . 'VERSION:' . $iCalVersion . $icalLineDelim
1539                . $this->prodID->getICal($icalLineDelim)
1540                . 'CALSCALE:GREGORIAN' . $icalLineDelim
1541                . 'METHOD:PUBLISH' . $icalLineDelim
1542
1543                // make Apple happy
1544                //
1545                . 'X-WR-CALNAME:' . $this->getName() . $icalLineDelim
1546                . 'X-WR-RELCALID:' . $this->id->getValue() . $icalLineDelim;
1547
1548
1549      // only show our own custom ID and name fields
1550      // if this calendar was created by this application
1551      //
1552      if ($this->getProductID() == $sm_cal_prodid)
1553         $iCalText .= $this->id->getICal($icalLineDelim)
1554//LEFT OFF HERE
1555//TODO: following property should have language and encoding info! (take care of in constructor??)
1556                    . $this->name->getICal($icalLineDelim);
1557
1558
1559      // unknown attributes are included, since they
1560      // were probably custom attributes defined by an
1561      // external source
1562      //
1563      foreach ($this->unknownAttributes as $name => $attr)
1564
1565         // already printed these
1566         //
1567         if ($name != 'X-WR-CALNAME' && $name != 'X-WR-RELCALID')
1568            $iCalText .= $attr . $icalLineDelim;
1569
1570
1571      // include all our internal attributes?
1572      //
1573      if ($includeExtras)
1574      {
1575         $iCalText .= $this->dom->getICal($icalLineDelim)
1576                    . $this->type->getICal($icalLineDelim)
1577                    . $this->sequence->getICal($icalLineDelim)
1578                    . $this->createdBy->getICal($icalLineDelim)
1579                    . $this->createdOn->getICal($icalLineDelim)
1580                    . $this->lastUpdatedBy->getICal($icalLineDelim)
1581                    . $this->lastUpdatedOn->getICal($icalLineDelim)
1582                    . $this->owners->getICal($icalLineDelim)
1583                    . $this->readable_users->getICal($icalLineDelim)
1584                    . $this->writeable_users->getICal($icalLineDelim);
1585      }
1586
1587
1588
1589      // if we need to dump all events, do that here right before ending the calendar
1590      //
1591      if (!empty($includeEventsForUser))
1592      {
1593         $this->retrieveAllEvents($includeEventsForUser);
1594         foreach ($this->allEvents as $event)
1595            $iCalText .= $event->getICal(FALSE, $icalLineDelim);
1596      }
1597
1598
1599
1600      $iCalText .= 'END:VCALENDAR' . $icalLineDelim;
1601
1602
1603      // fold lines that are too long
1604      //
1605      foldICalStreamByRef($iCalText);
1606
1607
1608      return $iCalText;
1609
1610   }
1611
1612
1613
1614   /**
1615     * Constructs a Calendar object from the given iCal stream
1616     *
1617     * @param array $iCalData The text value to be converted to a
1618     *                        Calendar object, one text line in
1619     *                        each array value.
1620     * @param string $fallbackName An additional name to be used if
1621     *                             none is given above or in any of the
1622     *                             calendar's extra attributes (optional)
1623     * @param string $uploadFilename A flag that indicates the source of this
1624     *                               calendar; if given, it represents the
1625     *                               filename from which it was uploaded,
1626     *                               in which case the ID (if not
1627     *                               given) will be constructed differently,
1628     *                               so that it is not create-time based.
1629     *                               (optional; default = empty string)
1630     *
1631     * @return object A Calendar object representing the given iCal stream.
1632     *
1633     * @access public
1634     *
1635     */
1636   function getCalendarFromICal($iCalText, $fallbackName='', $uploadFilename='')
1637   {
1638
1639      global $color;
1640
1641
1642      // strip out CRLFs from each line
1643      //
1644      foreach ($iCalText as $x => $line)
1645         $iCalText[$x] = str_replace(ICAL_LINE_DELIM, '', $line);
1646
1647
1648      // unfold text
1649      //
1650      unfoldICalStreamByRef($iCalText);
1651
1652
1653      $id = '';
1654      $dom = '';
1655      $type = '';
1656      $prodID = '';
1657      $name = '';
1658      $createdBy = '';
1659      $createdOn = '';
1660      $lastUpdatedBy = '';
1661      $lastUpdatedOn = '';
1662      $sequence = '';
1663      $owners = '';
1664      $writeable_users = '';
1665      $readable_users = '';
1666      $unknownAttributes = array();
1667      $embeddedEvents = array();
1668
1669
1670      // pull out properties
1671      //
1672      reset($iCalText);
1673      while (list($junk, $line) = each($iCalText))
1674      // use reset/each/next instead so we can use next below inside the loop
1675      // foreach ($iCalText as $line)
1676      {
1677
1678         $property = Property::extractICalProperty($line);
1679
1680
1681         // what do we do with each property?
1682         //
1683         switch ($property->getName())
1684         {
1685
1686            // skip this unless it is an embedded event
1687            //
1688            case 'BEGIN':
1689               if ($property->getValue() == 'VEVENT')
1690               {
1691
1692                  // build event array and pass to event parser
1693                  //
1694                  $event = array($line);
1695                  while (list($junk, $line) = each($iCalText))
1696                  {
1697                     $event[] = $line;
1698                     $property = Property::extractICalProperty($line);
1699                     if ($property->getName() == 'END')
1700                        break;
1701                  }
1702
1703                  $embeddedEvents[] = Event::getEventFromICal($event);
1704
1705               }
1706               break;
1707
1708
1709            // just skip these
1710            //
1711            case 'END':
1712               break;
1713
1714
1715            // version check
1716            //
1717            case 'VERSION':
1718               if ($property->getValue() != '2.0')
1719               {
1720                  plain_error_message('ERROR IN CALENDAR CLASS (getCalendarFromICal): Unsupported iCal version ' . $property->getValue(), $color);
1721                  exit;
1722               }
1723               break;
1724
1725
1726            // calendar scale check
1727            //
1728            case 'CALSCALE':
1729               if ($property->getValue() != 'GREGORIAN')
1730               {
1731                  plain_error_message('ERROR IN CALENDAR CLASS (getCalendarFromICal): Unsupported calendar scale ' . $property->getValue(), $color);
1732                  exit;
1733               }
1734               break;
1735
1736
1737            // product ID
1738            //
1739            case 'PRODID':
1740               $prodID = $property;
1741               break;
1742
1743
1744            // cal id for calendars created with this application
1745            //
1746            case 'X-SQ-CALID':
1747               $id = $property;
1748               break;
1749
1750
1751            // cal name for calendars created with this application
1752            //
1753            case 'X-SQ-CALNAME':
1754               $name = $property;
1755               break;
1756
1757
1758            // cal domain for calendars created with this application
1759            //
1760            case 'X-SQ-CALDOMAIN':
1761               $dom = $property;
1762               break;
1763
1764
1765            // cal type for calendars created with this application
1766            //
1767            case 'X-SQ-CALTYPE':
1768               $type = $property;
1769               break;
1770
1771
1772            // cal sequence for calendars created with this application
1773            //
1774            case 'X-SQ-CALSEQUENCE':
1775               $sequence = $property;
1776               break;
1777
1778
1779            // user who created this calendar for calendars created with this application
1780            //
1781            case 'X-SQ-CALCREATOR':
1782               $createdBy = $property;
1783               break;
1784
1785
1786            // creation date for calendars created with this application
1787            // (need to reparse the value as a date)
1788            //
1789            case 'X-SQ-CALCREATED':
1790               $createdOn = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE);
1791               break;
1792
1793
1794            // user who last modified this calendar for calendars created with this application
1795            //
1796            case 'X-SQ-CALLASTUPDATOR':
1797               $lastUpdatedBy = $property;
1798               break;
1799
1800
1801            // date last modified for calendars created with this application
1802            // (need to reparse the value as a date)
1803            //
1804            case 'X-SQ-CALLAST-MODIFIED':
1805               $lastUpdatedOn = Property::extractICalProperty($line, SM_CAL_ICAL_PROPERTY_TYPE_DATE);
1806               break;
1807
1808
1809            // cal owners for calendars created with this application
1810            //
1811            case 'X-SQ-CALOWNERS':
1812               $owners = $property;
1813               break;
1814
1815
1816            // cal writeable users for calendars created with this application
1817            //
1818            case 'X-SQ-CALWRITEABLEUSERS':
1819               $writeable_users = $property;
1820               break;
1821
1822
1823            // cal readable users for calendars created with this application
1824            //
1825            case 'X-SQ-CALREADABLEUSERS':
1826               $readable_users = $property;
1827               break;
1828
1829
1830            // unknown parameters just pile into this array
1831            //
1832            default:
1833               $unknownAttributes[$property->getName()] = $line;
1834               break;
1835
1836         }
1837
1838      }
1839
1840
1841      $cal = new Calendar($id, $sequence, $dom, $prodID, $type, $name,
1842                          $createdBy, $createdOn, $lastUpdatedBy, $lastUpdatedOn,
1843                          $owners, $readable_users, $writeable_users,
1844                          $unknownAttributes, $fallbackName, $uploadFilename);
1845
1846
1847      // add events (only if any were found)
1848      //
1849      if (sizeof($embeddedEvents))
1850      {
1851
1852//LEFT OFF HERE um, if this is the only place we call the caching function for external calendar events, will it not be able to cache events outside of this date range?  will it slow considerably if the user views months outside this range? does this help (getting it out of the FORM first)?
1853         sqgetGlobalVar('year', $year, SQ_FORM);
1854         if (empty($year))
1855            $year = date('Y');
1856
1857         $cacheDate = mktime(12, 0, 0, 1, 7, $year + 2);
1858
1859         $newEvents = array();
1860         $newEvents['onetime'] = array();
1861         $newEvents['recurring'] = array();
1862//TODO.... how do we store Todo items???
1863//         $newEvents['todo'] = array();
1864         foreach ($embeddedEvents as $event)
1865         {
1866            if ($event->isOneTime())
1867               $newEvents['onetime'][] = $event;
1868            else if ($event->isRecurring())
1869            {
1870               $newEvents['recurring'][] = $event;
1871            }
1872//TODO.... how do we store Todo items???
1873//            else if ($event->isTask())
1874//               $newEvents['todo'][] = $event;
1875         }
1876
1877         // precache recurring events
1878         //
1879         foreach (array_keys($newEvents['recurring']) as $index)
1880            $newEvents['recurring'][$index]->forceEventOccurrenceCache($cacheDate);
1881
1882
1883         // Add all events to the calendar now.
1884         //
1885         // Setting the third parameter below to TRUE means
1886         // we trust that all events are legitimate and no
1887         // duplicates exist; imported calendars that have
1888         // those problems should be fixed at the source anyway.
1889         // This also fixes problem when importing events w/no
1890         // IDs quickly while looping through uploaded iCal file,
1891         // timestamps are likely to match, so IDs will overlap,
1892         // but the TRUE below fixes that
1893         //
1894         $cal->addEvents($newEvents, 'xxx', TRUE, TRUE);
1895
1896      }
1897
1898
1899      return $cal;
1900
1901
1902   }
1903
1904
1905
1906}
1907
1908
1909
1910?>
1911