* Copyright (c) 2012 Bart Visscher * Copyright (c) 2012 Georg Ehrke * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ /** * This class manages our calendar objects */ use Auth; use App\Support\VObject; use Gdoo\Index\Services\AttachmentService; use Gdoo\Calendar\Models\Calendar; use Gdoo\Calendar\Models\CalendarObject; class CalendarService { /** * @brief timezone of the user */ public static $tz; /** * We need to specify a max date, because we need to stop *somewhere* * * On 32 bit system the maximum for a signed integer is 2147483647, so * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results * in 2038-01-19 to avoid problems when the date is converted * to a unix timestamp. */ const MAX_DATE = '2038-01-01'; /** * @return (string) $timezone as set by user or the default timezone */ public static function getTimezone() { self::$tz = date_default_timezone_get(); return self::$tz; } public static function getCalendars($userId, $active = false) { $model = Calendar::where('userid', $userId); if ($active) { $model->where('active', $active); } // 如果用户没有日历则添加一个默认的 if ($model->count() == 0) { static::addDefaultCalendar($userId); } $rows = $model->get(); return $rows; } public static function getCalendar($id, $security = true) { $calendar = Calendar::find($id); // FIXME: Correct arguments to just check for permissions if ($security === true) { if (Auth::id() == $calendar['userid']) { return $calendar; } else { return false; } } return $calendar; } public static function setCalendarActive($id, $active) { Calendar::where('id', $id)->update(array('active'=>$active)); } public static function addCalendar($userid, $name, $components='VEVENT,VTODO,VJOURNAL', $timezone=null, $order=0, $color=null) { $uri = self::createURI(); $data = array( 'userid' => $userid, 'displayname' => $name, 'principaluri' => 'principals/'.Auth::user()->id, 'uri' => $uri, 'ctag' => 1, 'calendarorder' => $order, 'calendarcolor' => $color, 'timezone' => $timezone, 'components' => $components ); return Calendar::insertGetId($data); } public static function editCalendar($id, $name = null, $components = null, $timezone = null, $order = null, $color = null) { $calendar = self::getCalendar($id, false); if ($calendar['userid'] != Auth::id()) { throw new \Exception('您没有权限编辑此日历。'); } if (is_null($name)) { $name = $calendar['displayname']; } if (is_null($components)) { $components = $calendar['components']; } if (is_null($timezone)) { $timezone = $calendar['timezone']; } if (is_null($order)) { $order = $calendar['calendarorder']; } if (is_null($color)) { $color = $calendar['calendarcolor']; } $data = array( 'displayname' => $name, 'calendarorder' => $order, 'calendarcolor' => $color, 'timezone' => $timezone, 'components' => $components, ); Calendar::where('id', $id)->update($data); self::touchCalendar($id); return $id; } /** * @brief gets the userid from a principal path * @return string */ public static function extractUserID($principaluri) { list($prefix, $userid) = \Sabre\DAV\URLUtil::splitPath($principaluri); return $userid; } public static function addDefaultCalendar($userid = 0) { if ($userid == 0) { $userid = Auth::id(); } $id = self::addCalendar($userid, '默认日历'); return true; } /** * @brief removes a calendar * @param integer $id * @return boolean */ public static function deleteCalendar($id) { $calendar = self::getCalendar($id, false); if ($calendar['userid'] != Auth::id()) { throw new \Exception('您没有权限删除此日历。'); } $events = CalendarObject::where('calendarid', $id)->get(); if (sizeof($events)) { foreach ($events as $event) { AttachmentService::remove($event->attachment); } CalendarObject::where('calendarid', $id)->delete(); } Calendar::where('id', $id)->delete(); return true; } public static function createURI() { return substr(md5(rand().time()), 0, 10); } /** * @brief Returns all objects of a calendar between $start and $end * @param integer $id * @param DateTime $start * @param DateTime $end * @return array * * The objects are associative arrays. You'll find the original vObject * in ['calendardata'] */ public static function getRangeEvents($id, $start, $end, $shared = false) { $start = strtotime($start); $end = strtotime($end); /* $model = CalendarObject::where(function ($q) use ($start, $end) { $q->where('rrule', 0); $q->whereBetween('firstoccurence', [$start, $end]); $q->orWhereBetween('lastoccurence', [$start, $end]); })->orWhere(function ($q) use ($start, $end) { $q->where('rrule', 1)->where('firstoccurence', '<=', $end); }); */ $model = CalendarObject::whereRaw('( (firstoccurence between '.$start.' and '.$end.' or lastoccurence between '.$start.' and '.$end.') or (rrule = 1 and firstoccurence <= '.$end.') )'); // 获取分享事件 if ($shared == true) { $model->whereIn('id', $id); } elseif (count($id)) { $model->whereIn('calendarid', $id); } $events = self::getRruleEvents($model->get(), $start, $end); return $events; } public static function getRruleEvents($rows, $start, $end) { $result = []; foreach ($rows as $key => $row) { $vcalendar = \Sabre\VObject\Reader::read($row->calendardata); $output = []; if ($vcalendar->name === 'VEVENT') { $vevent = $vcalendar; } elseif (isset($vcalendar->VEVENT)) { $vevent = $vcalendar->VEVENT; } else { return $output; } $allday = ($vcalendar->VEVENT->DTSTART->getDateType() == \Sabre\VObject\Property\DateTime::DATE) ? true : false; $output = array( 'id' => (int)$row->id, 'calendarid' => (int)$row->calendarid, 'title' => (isset($vevent->SUMMARY) && $vevent->SUMMARY->value) ? strtr($vevent->SUMMARY->value, array('\,' => ',', '\;' => ';')) : 'unnamed', 'description' => (isset($vevent->DESCRIPTION) && $vevent->DESCRIPTION->value) ? strtr($vevent->DESCRIPTION->value, array('\,' => ',', '\;' => ';')) : '', 'location' => (isset($vevent->LOCATION) && $vevent->LOCATION->value) ? strtr($vevent->LOCATION->value, array('\,' => ',', '\;' => ';')) : '', 'lastmodified' => $row->lastmodified, 'allDay' => $allday, ); if ($vcalendar->VEVENT->RRULE) { $start_dt = \DateTime::createFromFormat('U', $start); $end_dt = \DateTime::createFromFormat('U', $end); $vcalendar->expand($start_dt, $end_dt); } foreach ($vcalendar->getComponents() as $vevent) { if (!($vevent instanceof \Sabre\VObject\Component\VEvent)) { continue; } $rrule = self::generateStartEndDate($vevent->DTSTART, self::getDTEndFromVEvent($vevent), $allday, self::$tz); $result[] = array_merge($output, $rrule); } } return $result; } /** * @brief Returns all objects of a calendar * @param integer $id * @return array * * The objects are associative arrays. You'll find the original vObject in * ['calendardata'] */ public static function getEvent($id) { return CalendarObject::find($id); } /** * @brief Adds an object * @param integer $id * @param integer $calendardata * @param string $attachment * @return integer */ public static function add($id, $calendardata, $attachment = null) { $calendar = self::getCalendar($id); if ($calendar['userid'] != Auth::id()) { throw new \Exception('您没有权限添加事件到此日历。'); } $extraData = self::getDenormalizedData($calendardata); $uri = self::createURI().'.ics'; $data = array( 'calendarid' => $id, 'uri' => $uri, 'calendardata' => $calendardata, 'lastmodified' => time(), 'attachment' => $attachment, 'rrule' => $extraData['rrule'], 'etag' => $extraData['etag'], 'size' => $extraData['size'], 'componenttype' => $extraData['componentType'], 'firstoccurence' => $extraData['firstOccurence'], 'lastoccurence' => $extraData['lastOccurence'], ); $objectId = CalendarObject::insertGetId($data); self::touchCalendar($id); return $objectId; } /** * @brief edits an object * @param integer $id id of object * @param string $data object * @return boolean */ public static function edit($id, $calendardata, $attachment = null) { $event = self::getEvent($id); $calendar = self::getCalendar($event['calendarid']); if ($calendar['userid'] != Auth::id()) { throw new \Exception('您没有权限编辑此事件。'); } if (empty($attachment)) { $attachment = $event['attachment']; } $extraData = self::getDenormalizedData($calendardata); $data = array( 'calendardata' => $calendardata, 'lastmodified' => time(), 'attachment' => $attachment, 'rrule' => $extraData['rrule'], 'etag' => $extraData['etag'], 'size' => $extraData['size'], 'componenttype' => $extraData['componentType'], 'firstoccurence' => $extraData['firstOccurence'], 'lastoccurence' => $extraData['lastOccurence'], ); CalendarObject::where('id', $id)->update($data); self::touchCalendar($event['calendarid']); return true; } /** * @brief deletes an object * @param integer $id id of object * @return boolean */ public static function remove($id) { $event = self::getEvent($id); $calendar = self::getCalendar($event['calendarid']); if ($calendar['userid'] != Auth::id()) { throw new \Exception('您没有权限删除此事件。'); } AttachmentService::remove($event['attachment']); CalendarObject::where('id', $id)->delete(); self::touchCalendar($event['calendarid']); return true; } public static function moveToCalendar($id, $calendarid) { $calendar = self::getCalendar($calendarid); if ($calendar['userid'] != Auth::id()) { throw new \Exception('您没有权限添加事件到此日历。'); } CalendarObject::where('id', $id)->update(array('calendarid'=>$calendarid)); self::touchCalendar($calendarid); return true; } /** * Parses some information from calendar objects, used for optimized * calendar-queries. * * Returns an array with the following keys: * * etag * * size * * componentType * * firstOccurence * * lastOccurence * * @param string $calendarData * @return array */ public static function getDenormalizedData($calendarData) { $vObject = \Sabre\VObject\Reader::read($calendarData); $componentType = null; $component = null; $firstOccurence = null; $rrule = 0; foreach ($vObject->getComponents() as $component) { if ($component->name !== 'VTIMEZONE') { $componentType = $component->name; break; } } if (!$componentType) { throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); } if ($componentType === 'VEVENT') { $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->add(\VObject\DateTimeParser::parse($component->DURATION->getValue())); $lastOccurence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); $lastOccurence = $endDate->getTimeStamp(); } else { $lastOccurence = $firstOccurence; } } else { $it = new \Sabre\VObject\RecurrenceIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { $lastOccurence = $maxDate->getTimeStamp(); } else { $end = $it->getDtEnd(); while ($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurence = $end->getTimeStamp(); } $rrule = 1; } } return array( 'etag' => md5($calendarData), 'size' => strlen($calendarData), 'rrule' => $rrule, 'componentType' => $componentType, 'firstOccurence' => $firstOccurence, 'lastOccurence' => $lastOccurence, ); } /** * @brief returns the DTEND of an $vevent object * @param object $vevent vevent object * @return object */ public static function getDTEndFromVEvent($vevent) { if ($vevent->DTEND) { $dtend = $vevent->DTEND; } else { $dtend = clone $vevent->DTSTART; // clone creates a shallow copy, also clone DateTime $dtend->setDateTime(clone $dtend->getDateTime(), $dtend->getDateType()); if ($vevent->DURATION) { $duration = strval($vevent->DURATION); $invert = 0; if ($duration[0] == '-') { $duration = substr($duration, 1); $invert = 1; } if ($duration[0] == '+') { $duration = substr($duration, 1); } $interval = new \DateInterval($duration); $interval->invert = $invert; $dtend->getDateTime()->add($interval); } } return $dtend; } /** * @brief Get the permissions determined by the access class of an event/todo/journal * @param VObject $vobject Sabre VObject * @return (int) $permissions - CRUDS permissions * @see OCP\Share */ public static function getAccessClassPermissions($vobject) { if (isset($vobject->VEVENT)) { $velement = $vobject->VEVENT; } elseif (isset($vobject->VJOURNAL)) { $velement = $vobject->VJOURNAL; } elseif (isset($vobject->VTODO)) { $velement = $vobject->VTODO; } $accessclass = $velement->getAsString('CLASS'); return static::getAccessClassPermissions($accessclass); } /** * @brief returns the options for the access class of an event * @return array - valid inputs for the access class of an event */ public static function getAccessClassOptions() { return array( 'PUBLIC' => '共享', 'PRIVATE' => '私人', 'CONFIDENTIAL' => '保密', ); } /** * @brief returns the options for the repeat rule of an repeating event * @return array - valid inputs for the repeat rule of an repeating event */ public static function getValarmOptions() { return array( 'time' => array( 'PT0S' => '事件发生时', '-PT5M' => '5分钟前', '-PT15M' => '15分钟前', '-PT30M' => '30分钟前', '-PT1H' => '1小时前', '-PT2H' => '2小时前', '-P1D' => '1天前', '-P2D' => '2天前', '-P1W' => '1周前' ), 'day' => array( 'PT9H' => '事件发生当天(9:00)', '-PT15H' => '1天前(9:00)', '-P1DT15H' => '2天前(9:00)', '-P6DT15H' => '1周前' ) ); } /** * @brief returns the options for the repeat rule of an repeating event * @return array - valid inputs for the repeat rule of an repeating event */ public static function getRepeatOptions() { return array( 'doesnotrepeat' => ' - ', 'daily' => '每天', 'weekly' => '每周', 'weekday' => '每个工作日', 'biweekly' => '每两周', 'monthly' => '每月', 'yearly' => '每年' ); } /** * @brief returns the options for the end of an repeating event * @return array - valid inputs for the end of an repeating events */ public static function getEndOptions() { return array( 'never' => '从不', 'count' => '根据发生次', 'date' => '根据日期' ); } /** * @brief returns the options for an monthly repeating event * @return array - valid inputs for monthly repeating events */ public static function getMonthOptions() { return array( 'monthday' => '根据月天', 'weekday' => '根据星期' ); } /** * @brief returns the options for an weekly repeating event * @return array - valid inputs for weekly repeating events */ public static function getWeeklyOptions() { return array( 'MO' => '星期一', 'TU' => '星期二', 'WE' => '星期三', 'TH' => '星期四', 'FR' => '星期五', 'SA' => '星期六', 'SU' => '星期日' ); } /** * @brief returns the options for an monthly repeating event which occurs on specific weeks of the month * @return array - valid inputs for monthly repeating events */ public static function getWeekofMonth() { return array( 'auto' => '事件每月发生的周数', '1' => '首先', '2' => '其次', '3' => '第三', '4' => '第四', '5' => '第五', '-1' => '最后' ); } /** * @brief returns the options for an yearly repeating event which occurs on specific days of the year * @return array - valid inputs for yearly repeating events */ public static function getByYearDayOptions() { $return = array(); foreach (range(1, 366) as $num) { $return[(string)$num] = (string)$num; } return $return; } /** * @brief returns the options for an yearly or monthly repeating event which occurs on specific days of the month * @return array - valid inputs for yearly or monthly repeating events */ public static function getByMonthDayOptions() { $return = array(); foreach (range(1, 31) as $num) { $return[(string)$num] = (string)$num; } return $return; } /** * @brief returns the options for an yearly repeating event which occurs on specific month of the year * @return array - valid inputs for yearly repeating events */ public static function getByMonthOptions() { return array( '1' => '一月', '2' => '二月', '3' => '三月', '4' => '四月', '5' => '五月', '6' => '六月', '7' => '七月', '8' => '八月', '9' => '九月', '10' => '十月', '11' => '十一月', '12' => '十二月' ); } /** * @brief returns the options for an yearly repeating event * @return array - valid inputs for yearly repeating events */ public static function getYearOptions() { return array( 'bydate' => '根据时间日期', 'byyearday' => '根据年数', 'byweekno' => '根据周数', 'bydaymonth' => '根据天和月' ); } /** * @brief returns the options for an yearly repeating event which occurs on specific week numbers of the year * @return array - valid inputs for yearly repeating events */ public static function getByWeekNoOptions() { return range(1, 52); } /** * @brief validates a request * @param array $request * @return mixed (array / boolean) */ public static function validateRequest($request) { $errnum = 0; $errarr = array('title'=>'false', 'cal'=>'false', 'from'=>'false', 'fromtime'=>'false', 'to'=>'false', 'totime'=>'false', 'endbeforestart'=>'false'); if ($request['title'] == '') { $errarr['title'] = 'true'; $errnum++; } list($fromyear, $frommonth, $fromday) = explode('-', $request['from']); if (!checkdate($frommonth, $fromday, $fromyear)) { $errarr['from'] = 'true'; $errnum++; } $allday = isset($request['allday']); if (!$allday && self::checkTime(urldecode($request['fromtime']))) { $errarr['fromtime'] = 'true'; $errnum++; } list($toyear, $tomonth, $today) = explode('-', $request['to']); if (!checkdate($tomonth, $today, $toyear)) { $errarr['to'] = 'true'; $errnum++; } if ($request['repeat'] != 'doesnotrepeat') { if (is_nan($request['interval']) && $request['interval'] != '') { $errarr['interval'] = 'true'; $errnum++; } if (array_key_exists('repeat', $request) && !array_key_exists($request['repeat'], self::getRepeatOptions())) { $errarr['repeat'] = 'true'; $errnum++; } if (array_key_exists('advanced_month_select', $request) && !array_key_exists($request['advanced_month_select'], self::getMonthOptions())) { $errarr['advanced_month_select'] = 'true'; $errnum++; } if (array_key_exists('advanced_year_select', $request) && !array_key_exists($request['advanced_year_select'], self::getYearOptions())) { $errarr['advanced_year_select'] = 'true'; $errnum++; } if (array_key_exists('weekofmonthoptions', $request) && !array_key_exists($request['weekofmonthoptions'], self::getWeekofMonth())) { $errarr['weekofmonthoptions'] = 'true'; $errnum++; } if ($request['end'] != 'never') { if (!array_key_exists($request['end'], self::getEndOptions())) { $errarr['end'] = 'true'; $errnum++; } if ($request['end'] == 'count' && is_nan($request['byoccurrences'])) { $errarr['byoccurrences'] = 'true'; $errnum++; } if ($request['end'] == 'date') { list($bydate_year, $bydate_month, $bydate_day) = explode('-', $request['bydate']); if (!checkdate($bydate_month, $bydate_day, $bydate_year)) { $errarr['bydate'] = 'true'; $errnum++; } } } if (array_key_exists('weeklyoptions', $request)) { foreach ($request['weeklyoptions'] as $option) { if (!array_key_exists($option, self::getWeeklyOptions())) { $errarr['weeklyoptions'] = 'true'; $errnum++; } } } if (array_key_exists('byyearday', $request)) { foreach ($request['byyearday'] as $option) { if (!array_key_exists($option, self::getByYearDayOptions())) { $errarr['byyearday'] = 'true'; $errnum++; } } } if (array_key_exists('weekofmonthoptions', $request)) { if (is_nan((double)$request['weekofmonthoptions'])) { $errarr['weekofmonthoptions'] = 'true'; $errnum++; } } if (array_key_exists('bymonth', $request)) { foreach ($request['bymonth'] as $option) { if (!array_key_exists($option, self::getByMonthOptions())) { $errarr['bymonth'] = 'true'; $errnum++; } } } if (array_key_exists('byweekno', $request)) { foreach ($request['byweekno'] as $option) { if (!array_key_exists($option, self::getByWeekNoOptions())) { $errarr['byweekno'] = 'true'; $errnum++; } } } if (array_key_exists('bymonthday', $request)) { foreach ($request['bymonthday'] as $option) { if (!array_key_exists($option, self::getByMonthDayOptions())) { $errarr['bymonthday'] = 'true'; $errnum++; } } } } if (!$allday && self::checkTime(urldecode($request['totime']))) { $errarr['totime'] = 'true'; $errnum++; } if ($today < $fromday && $frommonth == $tomonth && $fromyear == $toyear) { $errarr['endbeforestart'] = 'true'; $errnum++; } if ($today == $fromday && $frommonth > $tomonth && $fromyear == $toyear) { $errarr['endbeforestart'] = 'true'; $errnum++; } if ($today == $fromday && $frommonth == $tomonth && $fromyear > $toyear) { $errarr['endbeforestart'] = 'true'; $errnum++; } if (!$allday && $fromday == $today && $frommonth == $tomonth && $fromyear == $toyear) { list($tohours, $tominutes) = explode(':', $request['totime']); list($fromhours, $fromminutes) = explode(':', $request['fromtime']); if ($tohours < $fromhours) { $errarr['endbeforestart'] = 'true'; $errnum++; } if ($tohours == $fromhours && $tominutes < $fromminutes) { $errarr['endbeforestart'] = 'true'; $errnum++; } } if ($errnum) { return $errarr; } return false; } /** * @brief validates time * @param string $time * @return boolean */ protected static function checkTime($time) { if (strpos($time, ':') === false) { return true; } list($hours, $minutes) = explode(':', $time); return empty($time) || $hours < 0 || $hours > 24 || $minutes < 0 || $minutes > 60; } /** * @brief creates an VCalendar Object from the request data * @param array $request * @return object created $vcalendar */ public static function createVCalendarFromRequest($request) { $vcalendar = new VObject('VCALENDAR'); $vcalendar->add('PRODID', 'gdoo.com Calendar'); $vcalendar->add('VERSION', '2.0'); $vevent = new VObject('VEVENT'); $vcalendar->add($vevent); $vevent->setDateTime('CREATED', 'now', \Sabre\VObject\Property\DateTime::UTC); $vevent->setUID(); return self::updateVCalendarFromRequest($request, $vcalendar); } /** * @brief updates an VCalendar Object from the request data * @param array $request * @param object $vcalendar * @return object updated $vcalendar */ public static function updateVCalendarFromRequest($request, $vobject) { $accessclass = $request["accessclass"]; $title = $request["title"]; $location = $request["location"]; $categories = $request["categories"]; $allday = isset($request["allday"]); $from = $request["from"]; $to = $request["to"]; if (!$allday) { $fromtime = $request['fromtime']; $totime = $request['totime']; } $vevent = $vobject->VEVENT; $description = $request["description"]; $repeat = $request["repeat"]; if ($repeat != 'doesnotrepeat') { $rrule = ''; $interval = $request['interval']; $end = $request['end']; $byoccurrences = $request['byoccurrences']; switch ($repeat) { case 'daily': $rrule .= 'FREQ=DAILY'; break; case 'weekly': $rrule .= 'FREQ=WEEKLY'; if (array_key_exists('weeklyoptions', $request)) { $byday = ''; foreach ($request['weeklyoptions'] as $days) { if ($byday == '') { $byday .= $days; } else { $byday .= ',' .$days; } } $rrule .= ';BYDAY=' . $byday; } break; case 'weekday': $rrule .= 'FREQ=WEEKLY'; $rrule .= ';BYDAY=MO,TU,WE,TH,FR'; break; case 'biweekly': $rrule .= 'FREQ=WEEKLY'; $interval = $interval * 2; break; case 'monthly': $rrule .= 'FREQ=MONTHLY'; if ($request['advanced_month_select'] == 'monthday') { break; } elseif ($request['advanced_month_select'] == 'weekday') { if ($request['weekofmonthoptions'] == 'auto') { list($_year, $_month, $_day) = explode('-', $from); $weekofmonth = floor($_day/7); } else { $weekofmonth = $request['weekofmonthoptions']; } $byday = ''; foreach ($request['weeklyoptions'] as $day) { if ($byday == '') { $byday .= $weekofmonth . $day; } else { $byday .= ',' . $weekofmonth . $day; } } if ($byday == '') { $byday = 'MO,TU,WE,TH,FR,SA,SU'; } $rrule .= ';BYDAY=' . $byday; } break; case 'yearly': $rrule .= 'FREQ=YEARLY'; if ($request['advanced_year_select'] == 'bydate') { } elseif ($request['advanced_year_select'] == 'byyearday') { list($_year, $_month, $_day) = explode('-', $from); $byyearday = date('z', mktime(0, 0, 0, $_month, $_day, $_year)) + 1; if (array_key_exists('byyearday', $request)) { foreach ($request['byyearday'] as $yearday) { $byyearday .= ',' . $yearday; } } $rrule .= ';BYYEARDAY=' . $byyearday; } elseif ($request['advanced_year_select'] == 'byweekno') { list($_year, $_month, $_day) = explode('-', $from); $rrule .= ';BYDAY=' . strtoupper(substr(date('l', mktime(0, 0, 0, $_month, $_day, $_year)), 0, 2)); $byweekno = ''; foreach ($request['byweekno'] as $weekno) { if ($byweekno == '') { $byweekno = $weekno; } else { $byweekno .= ',' . $weekno; } } $rrule .= ';BYWEEKNO=' . $byweekno; } elseif ($request['advanced_year_select'] == 'bydaymonth') { if (array_key_exists('weeklyoptions', $request)) { $byday = ''; foreach ($request['weeklyoptions'] as $day) { if ($byday == '') { $byday .= $day; } else { $byday .= ',' . $day; } } $rrule .= ';BYDAY=' . $byday; } if (array_key_exists('bymonth', $request)) { $bymonth = ''; foreach ($request['bymonth'] as $month) { if ($bymonth == '') { $bymonth .= $month; } else { $bymonth .= ',' . $month; } } $rrule .= ';BYMONTH=' . $bymonth; } if (array_key_exists('bymonthday', $request)) { $bymonthday = ''; foreach ($request['bymonthday'] as $monthday) { if ($bymonthday == '') { $bymonthday .= $monthday; } else { $bymonthday .= ',' . $monthday; } } $rrule .= ';BYMONTHDAY=' . $bymonthday; } } break; default: break; } if ($interval != '') { $rrule .= ';INTERVAL=' . $interval; } if ($end == 'count') { $rrule .= ';COUNT=' . $byoccurrences; } if ($end == 'date') { list($bydate_year, $bydate_month, $bydate_day) = explode('-', $request['bydate']); $until = $bydate_year . $bydate_month . $bydate_day; if ($allday) { $until = $until; } else { $until = $until.'T155959Z'; } $rrule .= ';UNTIL=' . $until; } $repeat = "true"; } else { $rrule = ''; $repeat = "false"; } $vevent->setString('RRULE', $rrule); $vevent->setDateTime('LAST-MODIFIED', 'now', \Sabre\VObject\Property\DateTime::UTC); $vevent->setDateTime('DTSTAMP', 'now', \Sabre\VObject\Property\DateTime::UTC); $vevent->setString('SUMMARY', $title); unset($vevent->VALARM); $valarm = \Sabre\VObject\Component::create('VALARM'); $valarm->ACTION = 'DTSTART'; $valarm->SUMMARY = 'Alarm notification'; if ($allday) { $start = new \DateTime($from); $end = new \DateTime($to.' +1 day'); $vevent->setDateTime('DTSTART', $start, \Sabre\VObject\Property\DateTime::DATE); $vevent->setDateTime('DTEND', $end, \Sabre\VObject\Property\DateTime::DATE); $valarm->TRIGGER = $request['valarm_day']; } else { $timezone = new \DateTimeZone(self::$tz); $start = new \DateTime($from.' '.$fromtime, $timezone); $end = new \DateTime($to.' '.$totime, $timezone); $vevent->setDateTime('DTSTART', $start, \Sabre\VObject\Property\DateTime::LOCALTZ); $vevent->setDateTime('DTEND', $end, \Sabre\VObject\Property\DateTime::LOCALTZ); $valarm->TRIGGER = $request['valarm_time']; } $vevent->add($valarm); unset($vevent->DURATION); $vevent->setString('CLASS', $accessclass); $vevent->setString('LOCATION', $location); $vevent->setString('DESCRIPTION', $description); $vevent->setString('CATEGORIES', $categories); return $vobject; } /** * @brief Updates ctag for calendar * @param integer $id * @return boolean */ public static function touchCalendar($id) { $calendar = Calendar::find($id); $calendar->ctag + 1; $calendar->save(); return true; } /** * @brief converts the start_dt and end_dt to a new timezone * @param object $dtstart * @param object $dtend * @param boolean $allday * @param string $tz * @return array */ public static function generateStartEndDate($dtstart, $dtend, $allday, $tz) { $start_dt = $dtstart->getDateTime(); $end_dt = $dtend->getDateTime(); $return = []; if ($allday) { $return['start'] = $start_dt->format('Y-m-d'); //$end_dt->modify('-1 minute'); while ($start_dt >= $end_dt) { $end_dt->modify('+1 day'); } $return['end'] = $end_dt->format('Y-m-d'); } else { if ($dtstart->getDateType() !== \Sabre\VObject\Property\DateTime::LOCAL) { $start_dt->setTimezone(new \DateTimeZone($tz)); $end_dt->setTimezone(new \DateTimeZone($tz)); } $return['start'] = $start_dt->format('Y-m-d H:i:s'); $return['end'] = $end_dt->format('Y-m-d H:i:s'); } return $return; } } CalendarService::getTimezone();