stream = $stream; $this->formatter = $formatter; } /** * @return CalendarStream */ public function getStreamObject() { return $this->stream; } /** * @return string * @throws \Exception */ public function getStream() { $this->stream->reset(); /* @var $cal Calendar */ foreach ($this->getCalendars() as $cal) { //start calendar $this->stream->addItem('BEGIN:VCALENDAR') ->addItem('VERSION:'.$cal->getVersion()) ->addItem('PRODID:'.$cal->getProdId()) ->addItem('CALSCALE:'.$cal->getCalendarScale()) ->addItem('METHOD:'.$cal->getMethod()); if ($cal->getName()) { $this->stream->addItem('NAME:'.$cal->getName()); } if ($cal->getColor()) { $this->stream->addItem('COLOR:'.$cal->getColor()); } if ($cal->getImage()) { $this->stream->addItem($this->formatter->getFormattedImageString($cal->getImage())); } //custom headers foreach ($cal->getCustomHeaders() as $key => $value) { $this->stream->addItem($key.':'.$value); } //timezone $this->stream->addItem('BEGIN:VTIMEZONE'); $tz = $cal->getTimezone(); $calEvents = $cal->getEvents(); // Fallback to current year $startYear = $endYear = date('Y'); if ($calEvents->first() !== false) { // Take first event as reference for timezone transitions $firstEvent = $calEvents->first(); $startYear = $firstEvent->getStart()->format('Y'); $endYear = $firstEvent->getEnd()->format('Y'); } $transitions = $tz->getTransitions(strtotime($startYear . '-01-01'), strtotime($endYear . '-12-31')); $daylightSavings = array( 'exists' => false, 'start' => '', 'offsetTo' => '', 'offsetFrom' => '' ); $standard = array( 'start' => '', 'offsetTo' => '', 'offsetFrom' => '' ); foreach ($transitions as $transition) { $varName = ($transition['isdst']) ? 'daylightSavings' : 'standard'; ${$varName}['exists'] = true; if ($this->dateTimeFormat === 'local') { ${$varName}['start'] = ':' . $this->formatter->getFormattedDateTime(new \DateTime($transition['time'])); } else if ($this->dateTimeFormat === 'utc') { ${$varName}['start'] = ':' . $this->formatter->getFormattedUTCDateTime(new \DateTime($transition['time'])); } else if ($this->dateTimeFormat == 'local-tz') { ${$varName}['start'] = ';' . $this->formatter->getFormattedDateTimeWithTimeZone(new \DateTime($transition['time'])); } ${$varName}['offsetTo'] = $this->formatter->getFormattedTimeOffset($transition['offset']); //get previous offset $previousTimezoneObservance = $transition['ts'] - 100; $tzDate = new \DateTime('now', $tz); $tzDate->setTimestamp($previousTimezoneObservance); $offset = $tzDate->getOffset(); ${$varName}['offsetFrom'] = $this->formatter->getFormattedTimeOffset($offset); } $this->stream->addItem('TZID:'.$tz->getName()); $this->stream->addItem('BEGIN:STANDARD') ->addItem('DTSTART'.$standard['start']) ->addItem('TZOFFSETTO:'.$standard['offsetTo']) ->addItem('TZOFFSETFROM:'.$standard['offsetFrom']); if ($daylightSavings['exists']) { $this->stream->addItem('RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU'); } $this->stream->addItem('END:STANDARD'); if ($daylightSavings['exists']) { $this->stream->addItem('BEGIN:DAYLIGHT') ->addItem('DTSTART'.$daylightSavings['start']) ->addItem('TZOFFSETTO:'.$daylightSavings['offsetTo']) ->addItem('TZOFFSETFROM:'.$daylightSavings['offsetFrom']) ->addItem('RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU') ->addItem('END:DAYLIGHT'); } $this->stream->addItem('END:VTIMEZONE'); //add events /* @var $event CalendarEvent */ foreach ($cal->getEvents() as $event) { if ($event->isAllDay()) { $dtStart = ':' . $this->formatter->getFormattedDate($event->getStart()); $dtEnd = ':' . $this->formatter->getFormattedDate($event->getEnd()); } else if ($this->dateTimeFormat === 'local') { $dtStart = ':' . $this->formatter->getFormattedDateTime($event->getStart()); $dtEnd = ':' . $this->formatter->getFormattedDateTime($event->getEnd()); } else if ($this->dateTimeFormat === 'utc') { $dtStart = ':' . $this->formatter->getFormattedUTCDateTime($event->getStart()); $dtEnd = ':' . $this->formatter->getFormattedUTCDateTime($event->getEnd()); } else if ($this->dateTimeFormat === 'local-tz'){ $dtStart = ';' . $this->formatter->getFormattedDateTimeWithTimeZone($event->getStart()); $dtEnd = ';' . $this->formatter->getFormattedDateTimeWithTimeZone($event->getEnd()); } $this->stream->addItem('BEGIN:VEVENT') ->addItem('UID:'.$event->getUid()) ->addItem('DTSTART'. $dtStart) ->addItem('DTEND'. $dtEnd); if ($event->getRecurrenceRule() instanceof RecurrenceRule) { $this->stream->addItem($event->getRecurrenceRule()->__toString()); } foreach ($event->getExceptionDates() as $date) { $this->stream->addItem('EXDATE:'.$this->formatter->getFormattedDateTime($date)); } if ($event->getSequence()) { $this->stream->addItem('SEQUENCE:'.$event->getSequence()); } if ($event->getTransp()) { $this->stream->addItem('TRANSP:'.$event->getTransp()); } if ($event->getColor()) { $this->stream->addItem('COLOR:'.$event->getColor()); } if ($event->getImage()) { $this->stream->addItem($this->formatter->getFormattedImageString($event->getImage())); } if ($event->getStatus()) { $this->stream->addItem('STATUS:'.$event->getStatus()); } $this->stream ->addItem('SUMMARY:'.$event->getSummary()) ->addItem('DESCRIPTION:'.$this->formatter->getEscapedText($event->getDescription())); if ($event->getClass()) { $this->stream->addItem('CLASS:'.$event->getClass()); } /* @var $location Location */ foreach ($event->getLocations() as $location) { $this->stream ->addItem('LOCATION'.$location->getUri().$location->getLanguage().':'.$this->formatter->getEscapedText($location->getName())); } if ($event->getPriority() > 0 && $event->getPriority() <= 9) { $this->stream->addItem('PRIORITY:'.$event->getPriority()); } if ($event->getGeo()) { $this->stream->addItem('GEO:'.$event->getGeo()->getLatitude().';'.$event->getGeo()->getLongitude()); } if ($event->getUrl()) { $this->stream->addItem('URL:'.$event->getUrl()); } if ($event->getTimestamp()) { $this->stream->addItem('DTSTAMP:'.$this->formatter->getFormattedUTCDateTime($event->getTimestamp())); } else { $this->stream->addItem('DTSTAMP:'.$this->formatter->getFormattedUTCDateTime(new \DateTime())); } if ($event->getCreated()) { $this->stream->addItem('CREATED:'.$this->formatter->getFormattedUTCDateTime($event->getCreated())); } if ($event->getLastModified()) { $this->stream->addItem('LAST-MODIFIED:'.$this->formatter->getFormattedUTCDateTime($event->getLastModified())); } foreach ($event->getAttendees() as $attendee) { $this->stream->addItem((string)$attendee); } if ($event->getOrganizer()) { $this->stream->addItem($event->getOrganizer()->__toString()); } foreach ($event->getCustomProperties() as $key => $value) { $this->stream->addItem($key.':'.$value); } /** @var CalendarAlarm $alarm */ foreach ($event->getAlarms() as $alarm) { //basic requirements for all types of alarm $this->stream->addItem('BEGIN:VALARM') ->addItem($this->formatTrigger($alarm->getTrigger())) ->addItem('ACTION:'.$alarm->getAction()); //only handle repeats if both repeat and duration are set if ($alarm->getRepeat() && $alarm->getDuration()) { $this->stream->addItem('REPEAT:'.$alarm->getRepeat()) ->addItem('DURATION:'.$this->formatter->getFormattedDateInterval($alarm->getDuration())); } //action specific logic switch ($alarm->getAction()) { case 'AUDIO': $attachments = $alarm->getAttachments(); $this->stream->addItem('ATTACH;'.$attachments[0]); break; case 'DISPLAY': $this->stream->addItem('DESCRIPTION:'.$alarm->getDescription()); break; case 'EMAIL': $this->stream->addItem('SUMMARY:'.$alarm->getSummary()) ->addItem('DESCRIPTION:'.$alarm->getDescription()); foreach ($alarm->getAttendees() as $attendee) { $this->stream->addItem((string)$attendee); } foreach ($alarm->getAttachments() as $attachment) { $this->stream->addItem('ATTACH;'.$attachment); } break; default: throw new \Exception("Unknown ALARM action: '{$alarm->getAction()}'"); break; } $this->stream->addItem('END:VALARM'); } foreach ($event->getConferences() as $conference) { $this->stream->addItem($this->formatter->getConferenceText($conference)); } $this->stream->addItem('END:VEVENT'); } //end calendar $this->stream->addItem('END:VCALENDAR'); } return $this->stream->getStream(); } /** * @return Calendar[] */ public function getCalendars() { return $this->calendars; } /** * @param array $calendars * @return CalendarExport */ public function setCalendars(array $calendars) { $this->calendars = $calendars; return $this; } /** * @param string $format * @return $this * @throws \Exception */ public function setDateTimeFormat($format) { if (in_array($format, ['local', 'local-tz', 'utc'])) { $this->dateTimeFormat = $format; } else { throw new \Exception('Invalid Format Chosen'); } return $this; } /** * @param Calendar $cal * @return CalendarExport */ public function addCalendar(Calendar $cal) { $this->calendars[] = $cal; return $this; } private function formatTrigger($trigger) { if ($trigger instanceof \DateInterval) { return 'TRIGGER:-' . $this->formatter->getFormattedDateInterval($trigger); } else { return 'TRIGGER;VALUE=DATE-TIME:'.$this->formatter->getFormattedUTCDateTime($trigger); } } }