diff --git a/src/Jsvrcek/ICS/CalendarExport.php b/src/Jsvrcek/ICS/CalendarExport.php index a337d0a..74e7d59 100755 --- a/src/Jsvrcek/ICS/CalendarExport.php +++ b/src/Jsvrcek/ICS/CalendarExport.php @@ -39,6 +39,14 @@ public function __construct(CalendarStream $stream, Formatter $formatter) $this->stream = $stream; $this->formatter = $formatter; } + + /** + * @return CalendarStream + */ + public function getStreamObject() + { + return $this->stream; + } /** * @return string @@ -136,7 +144,10 @@ public function getStream() if ($event->getRecurrenceRule() instanceof RecurrenceRule) $this->stream->addItem($event->getRecurrenceRule()->__toString()); - + + if ($event->getSequence()) + $this->stream->addItem('SEQUENCE:'.$event->getSequence()); + $this->stream->addItem('STATUS:'.$event->getStatus()) ->addItem('SUMMARY:'.$event->getSummary()) ->addItem('DESCRIPTION:'.$event->getDescription()); diff --git a/src/Jsvrcek/ICS/CalendarStream.php b/src/Jsvrcek/ICS/CalendarStream.php index de8fd87..5fe9444 100644 --- a/src/Jsvrcek/ICS/CalendarStream.php +++ b/src/Jsvrcek/ICS/CalendarStream.php @@ -9,6 +9,12 @@ class CalendarStream //length of line in bytes const LINE_LENGTH = 70; + /** + * echo items as they are set rather than building stream in memory, for use with streaming a large calendar + * + * @var boolean + */ + private $doImmediateOutput = false; /** * @@ -16,6 +22,15 @@ class CalendarStream */ private $stream = ''; + + /** + * @param boolean $doImmediateOutput + */ + public function setDoImmediateOutput($doImmediateOutput) + { + $this->doImmediateOutput = $doImmediateOutput; + } + /** * resets stream to blank string */ @@ -32,42 +47,45 @@ public function getStream() return $this->stream; } - /** - * splits item into new lines if necessary + /** + * splits item into new lines if necessary * @param string $item - * @return CalendarStream - */ - public function addItem($item) + * @return CalendarStream + */ + public function addItem($item) { - $line_breaks = array("\r\n","\n", "\r"); - $item = str_replace($line_breaks,'\n',$item); - //get number of bytes $length = strlen($item); $block = ''; - - if ($length > 75) + + if ($length > 75) { $start = 0; while ($start < $length) - { + { $block .= mb_strcut($item, $start, self::LINE_LENGTH, 'UTF-8'); $start = $start + self::LINE_LENGTH; //add space if not last line if ($start < $length) $block .= Constants::CRLF.' '; - } - } - else + } + } + else + { + $block = $item; + } + + $this->stream .= $block.Constants::CRLF; + + if ($this->doImmediateOutput) { - $block = $item; + echo $this; + $this->reset(); } - - $this->stream .= $block.Constants::CRLF; - return $this; + return $this; } /** @@ -77,4 +95,4 @@ public function __toString() { return $this->getStream(); } -} +} \ No newline at end of file diff --git a/src/Jsvrcek/ICS/Model/Calendar.php b/src/Jsvrcek/ICS/Model/Calendar.php index 82fd128..fe3213b 100644 --- a/src/Jsvrcek/ICS/Model/Calendar.php +++ b/src/Jsvrcek/ICS/Model/Calendar.php @@ -2,6 +2,8 @@ namespace Jsvrcek\ICS\Model; +use Jsvrcek\ICS\Utility\Provider; + class Calendar { /** @@ -39,24 +41,24 @@ class Calendar * @var \DateTimeZone $timezone */ private $timezone; - + /** - * - * @var array $events + * + * @var Provider */ - private $events = array(); + private $events; /** * - * @var array $todos + * @var Provider */ - private $todos = array(); + private $todos; /** * - * @var array $freeBusy + * @var Provider */ - private $freeBusy = array(); + private $freeBusy; /** * @@ -64,6 +66,33 @@ class Calendar public function __construct() { $this->timezone = new \DateTimeZone('America/New_York'); + $this->events = new Provider(); + $this->todos = new Provider(); + $this->freeBusy = new Provider(); + } + + /** + * For use if you want CalendarExport::getStream to get events in batches from a database during + * the output of the ics feed, instead of adding all events to the Calendar object before outputting + * the ics feed. + * - CalendarExport::getStream iterates through the Calendar::$events internal data array. The $eventsProvider + * closure will be called every time this data array reaches its end during iteration, and the closure should + * return the next batch of events + * - A $startKey argument with the current key of the data array will be passed to the $eventsProvider closure + * - The $eventsProvider must return an array of CalendarEvent objects + * + * Example: Calendar::setEventsProvider(function($startKey){ + * //get database rows starting with $startKey + * //return an array of CalendarEvent objects + * }) + * + * @param \Closure $eventsProvider + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setEventsProvider(\Closure $eventsProvider) + { + $this->events = new Provider($eventsProvider); + return $this; } /** @@ -189,7 +218,7 @@ public function setTimezone(\DateTimeZone $timezone) } /** - * @return array + * @return Provider */ public function getEvents() { @@ -202,7 +231,7 @@ public function getEvents() */ public function addEvent(CalendarEvent $event) { - $this->events[] = $event; + $this->events->add($event); return $this; } diff --git a/src/Jsvrcek/ICS/Model/CalendarEvent.php b/src/Jsvrcek/ICS/Model/CalendarEvent.php index 7c2ca59..f6fba0d 100755 --- a/src/Jsvrcek/ICS/Model/CalendarEvent.php +++ b/src/Jsvrcek/ICS/Model/CalendarEvent.php @@ -107,6 +107,12 @@ class CalendarEvent */ private $recuringId; + /** + * + * @var integer + */ + private $sequence; + /** * * @var array $attendees @@ -486,6 +492,24 @@ public function setRecuringId($recuringId) $this->recuringId = $recuringId; return $this; } + + /** + * @return integer + */ + public function getSequence() + { + return $this->sequence; + } + + /** + * @param integer $sequence + * @return \Jsvrcek\ICS\Model\CalendarEvent + */ + public function setSequence($sequence) + { + $this->sequence = $sequence; + return $this; + } /** * diff --git a/src/Jsvrcek/ICS/Utility/Provider.php b/src/Jsvrcek/ICS/Utility/Provider.php new file mode 100644 index 0000000..bc1cd7a --- /dev/null +++ b/src/Jsvrcek/ICS/Utility/Provider.php @@ -0,0 +1,110 @@ +provider = $provider; + } + + /** + * for manually adding items, rather than using a provider closure to add items in batches during iteration + * Cannot be used in conjunction with a provider closure! + * + * @param mixed $item + */ + public function add($item) + { + $this->manuallyAddedData[] = $item; + } + + /* (non-PHPdoc) + * @see Iterator::current() + */ + public function current() + { + return current($this->data); + } + + /* (non-PHPdoc) + * @see Iterator::key() + */ + public function key() + { + return $this->key; + } + + /* (non-PHPdoc) + * @see Iterator::next() + */ + public function next() + { + array_shift($this->data); + $this->key++; + } + + /* (non-PHPdoc) + * @see Iterator::rewind() + */ + public function rewind() + { + $this->data = array(); + $this->key = 0; + } + + /** + * get next batch from provider if data array is at the end + * + * (non-PHPdoc) + * @see Iterator::valid() + */ + public function valid() + { + if (count($this->data) < 1) + { + if ($this->provider instanceof \Closure) + { + $this->data = $this->provider->__invoke($this->key); + } + else + { + $this->data = $this->manuallyAddedData; + $this->manuallyAddedData = array(); + } + } + + return count($this->data) > 0; + } +} \ No newline at end of file diff --git a/tests/Jsvrcek/ICS/Tests/CalendarExportTest.php b/tests/Jsvrcek/ICS/Tests/CalendarExportTest.php index da301d2..1636631 100644 --- a/tests/Jsvrcek/ICS/Tests/CalendarExportTest.php +++ b/tests/Jsvrcek/ICS/Tests/CalendarExportTest.php @@ -50,7 +50,8 @@ public function testGetStream() ->setEnd(new \DateTime('31 October 2013')) ->setSummary('Oktoberfest at the South Pole') ->addAttendee($attendee) - ->setOrganizer($organizer); + ->setOrganizer($organizer) + ->setSequence(3); $rrule = new RecurrenceRule(new Formatter()); $rrule->setFrequency(new Frequency(Frequency::MONTHLY)) @@ -64,8 +65,30 @@ public function testGetStream() ->setTimezone(new \DateTimeZone('Antarctica/McMurdo')) ->addEvent($event); + //create second calendar using batch event provider + $calTwo = new Calendar(); + $calTwo->setProdId('-//Jsvrcek//ICS//EN2') + ->setTimezone(new \DateTimeZone('Arctic/Longyearbyen')); + + $calTwo->setEventsProvider(function($start){ + $eventOne = new CalendarEvent(); + $eventOne->setUid('asdfasdf@example.com') + ->setStart(new \DateTime('2016-01-01 01:01:01')) + ->setEnd(new \DateTime('2016-01-02 01:01:01')) + ->setSummary('A long day'); + + $eventTwo = new CalendarEvent(); + $eventTwo->setUid('asdfasdf@example.com') + ->setStart(new \DateTime('2016-01-02 01:01:01')) + ->setEnd(new \DateTime('2016-01-03 01:01:01')) + ->setSummary('Another long day'); + + return ($start > 0) ? array() : array($eventOne, $eventTwo); + }); + $ce = new CalendarExport(new CalendarStream(), new Formatter()); - $ce->addCalendar($cal); + $ce->addCalendar($cal) + ->addCalendar($calTwo); $stream = $ce->getStream(); diff --git a/tests/test.ics b/tests/test.ics index 7255b25..77599c7 100644 --- a/tests/test.ics +++ b/tests/test.ics @@ -16,6 +16,7 @@ UID:lLKjd89283oja89282lkjd8@example.com DTSTART:20131001T000000Z DTEND:20131031T000000Z RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=6;BYDAY=2SA +SEQUENCE:3 STATUS: SUMMARY:Oktoberfest at the South Pole DESCRIPTION: @@ -26,3 +27,33 @@ ORGANIZER;SENT-BY="mary@example.com";CN=Sue Jones;LANGUAGE=en:mailto:s ue@example.com END:VEVENT END:VCALENDAR +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Jsvrcek//ICS//EN2 +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VTIMEZONE +TZID:Arctic/Longyearbyen +BEGIN:STANDARD +DTSTART:19700101T000000 +TZOFFSETTO:+0100 +TZOFFSETFROM:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:asdfasdf@example.com +DTSTART:20160101T010101Z +DTEND:20160102T010101Z +STATUS: +SUMMARY:A long day +DESCRIPTION: +END:VEVENT +BEGIN:VEVENT +UID:asdfasdf@example.com +DTSTART:20160102T010101Z +DTEND:20160103T010101Z +STATUS: +SUMMARY:Another long day +DESCRIPTION: +END:VEVENT +END:VCALENDAR