1131 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			1131 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php namespace Gdoo\Calendar\Services;
 | |
| 
 | |
| /**
 | |
|  * Copyright (c) 2011 Jakob Sack <mail@jakobsack.de>
 | |
|  * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl>
 | |
|  * Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de>
 | |
|  * 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(); |