1185 lines
41 KiB
PHP
1185 lines
41 KiB
PHP
<?php namespace Gdoo\Calendar\Services;
|
|
|
|
/**
|
|
* 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;
|
|
use Gdoo\Calendar\Models\CalendarReminder;
|
|
use Gdoo\Index\Services\ShareService;
|
|
|
|
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()) {
|
|
abort_error('您没有权限编辑此日历。');
|
|
}
|
|
|
|
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()) {
|
|
abort_error('您没有权限删除此日历。');
|
|
}
|
|
$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 array $id
|
|
* @param int $start
|
|
* @param int $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::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($params, $vcalendar)
|
|
{
|
|
$calendarid = $params['calendarid'];
|
|
$calendar = self::getCalendar($calendarid);
|
|
|
|
if ($calendar['userid'] != Auth::id()) {
|
|
abort_error('您没有权限添加事件到此日历。');
|
|
}
|
|
|
|
$calendardata = $vcalendar->serialize();
|
|
$extraData = self::getDenormalizedData($calendardata);
|
|
$uri = self::createURI().'.ics';
|
|
$data = [
|
|
'calendarid' => $calendarid,
|
|
'calendardata' => $calendardata,
|
|
'uri' => $uri,
|
|
'rrule' => $extraData['rrule'],
|
|
'etag' => $extraData['etag'],
|
|
'size' => $extraData['size'],
|
|
'lastmodified' => time(),
|
|
'attachment' => $params['attachment'],
|
|
'componenttype' => $extraData['componentType'],
|
|
'firstoccurence' => $extraData['firstOccurence'],
|
|
'lastoccurence' => $extraData['lastOccurence'],
|
|
];
|
|
$objectId = CalendarObject::insertGetId($data);
|
|
|
|
// 是重复事件
|
|
$is_recurring = $vcalendar->VEVENT->RRULE ? 1 : 0;
|
|
|
|
// 事件提醒
|
|
if (isset($vcalendar->VEVENT->VALARM)) {
|
|
$triggerTime = $vcalendar->VEVENT->VALARM->getEffectiveTriggerTime();
|
|
$valarm_at = $triggerTime->getTimeStamp();
|
|
CalendarReminder::insert([
|
|
'calendar_id' => $calendarid,
|
|
'object_id' => $objectId,
|
|
'is_recurring' => $is_recurring,
|
|
'alarm_at' => $valarm_at,
|
|
]);
|
|
}
|
|
|
|
// 写入共享数据
|
|
ShareService::addItem([
|
|
'source_id' => $objectId,
|
|
'source_type' => 'event',
|
|
'is_repeat' => $is_recurring,
|
|
'receive_id' => $params['receive_id'],
|
|
'receive_name' => $params['receive_name'],
|
|
'start_at' => $extraData['firstOccurence'],
|
|
'end_at' => $extraData['lastOccurence'],
|
|
]);
|
|
|
|
self::touchCalendar($calendarid);
|
|
return $objectId;
|
|
}
|
|
|
|
/**
|
|
* @brief edits an object
|
|
* @param integer $id id of object
|
|
* @param string $data object
|
|
* @return boolean
|
|
*/
|
|
public static function edit($params, $vcalendar)
|
|
{
|
|
$event = self::getEvent($params['id']);
|
|
$calendar = self::getCalendar($event['calendarid']);
|
|
|
|
if ($calendar['userid'] != Auth::id()) {
|
|
abort_error('您没有权限编辑此事件。');
|
|
}
|
|
|
|
$calendardata = $vcalendar->serialize();
|
|
$extraData = self::getDenormalizedData($calendardata);
|
|
$data = [
|
|
'calendardata' => $calendardata,
|
|
'lastmodified' => time(),
|
|
'rrule' => $extraData['rrule'],
|
|
'etag' => $extraData['etag'],
|
|
'size' => $extraData['size'],
|
|
'componenttype' => $extraData['componentType'],
|
|
'firstoccurence' => $extraData['firstOccurence'],
|
|
'lastoccurence' => $extraData['lastOccurence'],
|
|
];
|
|
|
|
// 存在附件字段
|
|
if (isset($params['attachment'])) {
|
|
$data['attachment'] = $params['attachment'];
|
|
}
|
|
|
|
CalendarObject::where('id', $params['id'])->update($data);
|
|
|
|
// 是重复事件
|
|
$is_recurring = $vcalendar->VEVENT->RRULE ? 1 : 0;
|
|
|
|
// 事件提醒
|
|
if ($vcalendar->VEVENT->VALARM->TRIGGER) {
|
|
$triggerTime = $vcalendar->VEVENT->VALARM->getEffectiveTriggerTime();
|
|
$valarm_at = $triggerTime->getTimeStamp();
|
|
$reminder = CalendarReminder::firstOrNew(['calendar_id' => $event['calendarid'], 'object_id' => $event['id']]);
|
|
$reminder->is_recurring = $is_recurring;
|
|
$reminder->alarm_at = $valarm_at;
|
|
$reminder->save();
|
|
} else {
|
|
CalendarReminder::where('object_id', $event['id'])->delete();
|
|
}
|
|
|
|
// 修改共享数据
|
|
$share_data = [
|
|
'source_id' => $params['id'],
|
|
'source_type' => 'event',
|
|
'is_repeat' => $is_recurring,
|
|
'start_at' => $extraData['firstOccurence'],
|
|
'end_at' => $extraData['lastOccurence'],
|
|
];
|
|
|
|
// 存在接收人字段
|
|
if (isset($params['receive_id'])) {
|
|
$share_data['receive_id'] = $params['receive_id'];
|
|
$share_data['receive_name'] = $params['receive_name'];
|
|
}
|
|
|
|
$share = ShareService::getItem('event', $params['id']);
|
|
if (empty($share)) {
|
|
ShareService::addItem($share_data);
|
|
} else {
|
|
ShareService::editItem('event', $params['id'], $share_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()) {
|
|
abort_error('您没有权限删除此事件。');
|
|
}
|
|
|
|
AttachmentService::remove($event['attachment']);
|
|
CalendarObject::where('id', $id)->delete();
|
|
CalendarReminder::where('object_id', $id)->delete();
|
|
self::touchCalendar($event['calendarid']);
|
|
return true;
|
|
}
|
|
|
|
public static function moveToCalendar($id, $calendarid)
|
|
{
|
|
$calendar = self::getCalendar($calendarid);
|
|
if ($calendar['userid'] != Auth::id()) {
|
|
abort_error('您没有权限添加事件到此日历。');
|
|
}
|
|
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(); |