mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 09:23:28 +01:00
WIP InfoLog REST API
This commit is contained in:
parent
f568930a90
commit
37ebc4b8e3
@ -1153,7 +1153,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
|||||||
{
|
{
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
$is_addressbook = strpos($options['path'], '/addressbook') !== false;
|
$is_addressbook = strpos($options['path'], '/addressbook') !== false;
|
||||||
$is_calendar = strpos($options['path'], '/calendar') !== false;
|
$is_calendar = (bool)preg_match('#/(calendar|infolog)#', $options['path']);
|
||||||
$propfind_options = array(
|
$propfind_options = array(
|
||||||
'path' => $options['path'],
|
'path' => $options['path'],
|
||||||
'depth' => 1,
|
'depth' => 1,
|
||||||
|
@ -148,12 +148,13 @@ class JsBase
|
|||||||
* Return EGroupware custom fields
|
* Return EGroupware custom fields
|
||||||
*
|
*
|
||||||
* @param array $contact
|
* @param array $contact
|
||||||
|
* @param ?string $app default self::APP
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function customfields(array $contact)
|
protected static function customfields(array $contact, ?string $app=null)
|
||||||
{
|
{
|
||||||
$fields = [];
|
$fields = [];
|
||||||
foreach(Api\Storage\Customfields::get(static::APP) as $name => $data)
|
foreach(Api\Storage\Customfields::get($app ?? static::APP) as $name => $data)
|
||||||
{
|
{
|
||||||
$value = $contact['#'.$name];
|
$value = $contact['#'.$name];
|
||||||
if (isset($value))
|
if (isset($value))
|
||||||
@ -191,12 +192,13 @@ class JsBase
|
|||||||
* Not send custom fields are set to null!
|
* Not send custom fields are set to null!
|
||||||
*
|
*
|
||||||
* @param array $cfs name => object with attribute data and optional type, label, values
|
* @param array $cfs name => object with attribute data and optional type, label, values
|
||||||
|
* @param ?string $app default self::APP
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function parseCustomfields(array $cfs)
|
protected static function parseCustomfields(array $cfs, ?string $app=null)
|
||||||
{
|
{
|
||||||
$contact = [];
|
$contact = [];
|
||||||
$definitions = Api\Storage\Customfields::get(static::APP);
|
$definitions = Api\Storage\Customfields::get($app ?? static::APP);
|
||||||
|
|
||||||
foreach($definitions as $name => $definition)
|
foreach($definitions as $name => $definition)
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,7 @@ class JsCalendar extends JsBase
|
|||||||
const MIME_TYPE_JSTASK = "application/jscalendar+json;type=task";
|
const MIME_TYPE_JSTASK = "application/jscalendar+json;type=task";
|
||||||
|
|
||||||
const TYPE_EVENT = 'Event';
|
const TYPE_EVENT = 'Event';
|
||||||
|
const TYPE_TASK = 'Task';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get JsEvent for given event
|
* Get JsEvent for given event
|
||||||
@ -206,6 +207,228 @@ class JsCalendar extends JsBase
|
|||||||
return $event;
|
return $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get JsEvent for given event
|
||||||
|
*
|
||||||
|
* @param int|array $entry
|
||||||
|
* @param bool|"pretty" $encode true: JSON encode, "pretty": JSON encode with pretty-print, false: return raw data e.g. from listing
|
||||||
|
* @param ?array $exceptions=null
|
||||||
|
* @return string|array
|
||||||
|
* @throws Api\Exception\NotFound
|
||||||
|
*/
|
||||||
|
public static function JsTask($entry, $encode=true, array $exceptions=[])
|
||||||
|
{
|
||||||
|
if (is_scalar($entry) && !($entry = self::getInfolog()->read($entry, false, 'object')))
|
||||||
|
{
|
||||||
|
throw new Api\Exception\NotFound();
|
||||||
|
}
|
||||||
|
$data = [
|
||||||
|
self::AT_TYPE => self::TYPE_TASK,
|
||||||
|
'prodId' => 'EGroupware InfoLog '.$GLOBALS['egw_info']['apps']['api']['version'],
|
||||||
|
'uid' => self::uid($entry['info_uid']),
|
||||||
|
'sequence' => $entry['info_etag'],
|
||||||
|
'created' => self::UTCDateTime($entry['info_created']),
|
||||||
|
'updated' => self::UTCDateTime($entry['info_modified']),
|
||||||
|
'title' => $entry['info_subject'],
|
||||||
|
'start' => $entry['info_startdate'] ? self::DateTime($entry['info_startdate'], Api\DateTime::$user_timezone->getName()) : null,
|
||||||
|
'showWithoutTime' => $no_time = Api\DateTime::to($entry['info_startdate'], 'H:i') === '00:00',
|
||||||
|
'timeZone' => Api\DateTime::$user_timezone->getName(),
|
||||||
|
'due' => $entry['info_enddate'] ? self::DateTime($entry['info_enddate'], Api\DateTime::$user_timezone->getName()) : null,
|
||||||
|
'duration' => $entry['info_used_time'] ?
|
||||||
|
self::Duration(0, $entry['info_used_time']*60) : null,
|
||||||
|
'estimatedDuration' => $entry['info_plannedtime'] ?
|
||||||
|
self::Duration(0, $entry['info_plannedtime']*60) : null,
|
||||||
|
'recurrenceRules' => isset($entry['#RRULE']) ? null : null,
|
||||||
|
'recurrenceOverrides' => null,
|
||||||
|
//'freeBusyStatus' => $entry['non_blocking'] ? 'free' : null, // default is busy
|
||||||
|
'description' => $entry['info_des'],
|
||||||
|
'participants' => self::Responsible($entry),
|
||||||
|
//'alerts' => self::Alerts($entry['alarm']),
|
||||||
|
'status' => in_array($entry['info_status'], ['deleted', 'cancelled']) ? 'cancelled' :
|
||||||
|
($entry['info_status'] === 'offer' ? 'tentative' : 'confirmed'),
|
||||||
|
'progress' => self::Progress($entry['info_status']),
|
||||||
|
'priority' => isset($entry['info_priority']) ? self::Priority($entry['info_priority']) : null,
|
||||||
|
'categories' => self::categories($entry['info_cat']),
|
||||||
|
'privacy' => $entry['info_access'],
|
||||||
|
'percentComplete' => (int)$entry['info_percent'],
|
||||||
|
'egroupware.org:type' => $entry['info_type'],
|
||||||
|
'egroupware.org:completed' => $entry['info_datecomplete'] ?
|
||||||
|
self::DateTime($entry['info_datecompleted'], Api\DateTime::$user_timezone->getName()) : null,
|
||||||
|
'egroupware.org:customfields' => self::customfields($entry, 'infolog'),
|
||||||
|
] + self::Locations(['location' => $entry['info_location'] ?? null]);
|
||||||
|
|
||||||
|
if (!empty($entry['##RRULE']))
|
||||||
|
{
|
||||||
|
$data = array_merge($data, self::cfRrule2recurrenceRules($entry));
|
||||||
|
}
|
||||||
|
$data = array_filter($data);
|
||||||
|
|
||||||
|
if ($encode)
|
||||||
|
{
|
||||||
|
return Api\CalDAV::json_encode($data, $encode === "pretty");
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse JsEvent
|
||||||
|
*
|
||||||
|
* We use strict parsing for "application/jscalendar+json" content-type, not for "application/json".
|
||||||
|
* Strict parsing checks objects for proper @type attributes and value attributes, non-strict allows scalar values.
|
||||||
|
*
|
||||||
|
* Non-strict parsing also automatic detects patch for POST requests.
|
||||||
|
*
|
||||||
|
* @param string $json
|
||||||
|
* @param array $old=[] existing contact for patch
|
||||||
|
* @param ?string $content_type=null application/json no strict parsing and automatic patch detection, if method not 'PATCH' or 'PUT'
|
||||||
|
* @param string $method='PUT' 'PUT', 'POST' or 'PATCH'
|
||||||
|
* @param ?int $calendar_owner owner of the collection
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function parseJsTask(string $json, array $old=[], string $content_type=null, $method='PUT', int $calendar_owner=null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$strict = !isset($content_type) || !preg_match('#^application/json#', $content_type);
|
||||||
|
$data = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
// check if we use patch: method is PATCH or method is POST AND keys contain slashes
|
||||||
|
if ($method === 'PATCH' || !$strict && $method === 'POST' && array_filter(array_keys($data), static function ($key)
|
||||||
|
{
|
||||||
|
return strpos($key, '/') !== false;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
// apply patch on JsEvent
|
||||||
|
$data = self::patch($data, $old ? self::getJsTask($old, false) : [], !$old || !$strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||||
|
|
||||||
|
$event = [];
|
||||||
|
foreach ($data as $name => $value)
|
||||||
|
{
|
||||||
|
switch ($name)
|
||||||
|
{
|
||||||
|
case 'uid':
|
||||||
|
$event['info_uid'] = self::parseUid($value, $old['info_uid'], !$strict);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'title':
|
||||||
|
$event['info_subject'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'description':
|
||||||
|
$event['info_des'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'start':
|
||||||
|
case 'duration':
|
||||||
|
case 'timeZone':
|
||||||
|
case 'showWithoutTime':
|
||||||
|
if (!isset($event['start']))
|
||||||
|
{
|
||||||
|
$event += self::parseStartDuration($data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'participants':
|
||||||
|
$event += self::parseParticipants($value, $strict, $calendar_owner);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'priority':
|
||||||
|
$event['priority'] = self::parsePriority($value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'privacy':
|
||||||
|
$event['info_public'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'recurrenceRules':
|
||||||
|
$event += self::parseRecuranceRules2cfRrule($data['recurrenceRules']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'categories':
|
||||||
|
$event['info_cat'] = (int)self::parseCategories($value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'egroupware.org:customfields':
|
||||||
|
$event = array_merge($event, self::parseCustomfields($value, $strict));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'egroupware.org:completed':
|
||||||
|
$event['info_datecomplete'] = self::parseDateTime();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'egroupware.org:type':
|
||||||
|
$event['info_type'] = $value;
|
||||||
|
|
||||||
|
case 'prodId':
|
||||||
|
case 'created':
|
||||||
|
case 'updated':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error_log(__METHOD__ . "() $name=" . json_encode($value, self::JSON_OPTIONS_ERROR) . ' --> ignored');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Throwable $e) {
|
||||||
|
self::handleExceptions($e, 'JsCalendar Event', $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no participant given add current user as CHAIR to the event
|
||||||
|
if (empty($event['participants']))
|
||||||
|
{
|
||||||
|
$event['participants'][$calendar_owner ?? $GLOBALS['egw_info']['user']['account_id']] = 'ACHAIR';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static $status2progress = [
|
||||||
|
'offer' => null,
|
||||||
|
'not-started' => 'needs-action',
|
||||||
|
'ongoing' => 'in-progress',
|
||||||
|
'done' => 'completed',
|
||||||
|
'cancelled' => 'cancelled',
|
||||||
|
'billed' => null,
|
||||||
|
'template' => null,
|
||||||
|
'nonactive' => null,
|
||||||
|
'archive' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an InfoLog status to a JsTask progress
|
||||||
|
*
|
||||||
|
* @link https://datatracker.ietf.org/doc/html/rfc8984#name-progress
|
||||||
|
* @param string $status
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function Progress(string $info_status)
|
||||||
|
{
|
||||||
|
return self::$status2progress[$info_status] ?? 'egroupware.org:'.$info_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $progress
|
||||||
|
* @param string $info_type
|
||||||
|
* @return string known infolog status, or "not-started"
|
||||||
|
*/
|
||||||
|
protected static function parseProgress(string $progress, string $info_type=null)
|
||||||
|
{
|
||||||
|
if (!($status = array_search($progress, self::$status2progress)))
|
||||||
|
{
|
||||||
|
if (!str_starts_with('egroupware.org:', $progress) ||
|
||||||
|
($status = substr($progress, strlen('egroupware.org:'))) &&
|
||||||
|
isset($info_type) && !isset(self::getInfolog()->status[$info_type][$status]))
|
||||||
|
{
|
||||||
|
$status = 'not-started';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse categories object
|
* Parse categories object
|
||||||
*
|
*
|
||||||
@ -267,6 +490,7 @@ class JsCalendar extends JsBase
|
|||||||
*
|
*
|
||||||
* @link https://datatracker.ietf.org/doc/html/rfc8984#name-localdatetime
|
* @link https://datatracker.ietf.org/doc/html/rfc8984#name-localdatetime
|
||||||
* @param null|string|\DateTime $date
|
* @param null|string|\DateTime $date
|
||||||
|
* @param string $timezone
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
protected static function DateTime($date, $timezone)
|
protected static function DateTime($date, $timezone)
|
||||||
@ -293,7 +517,7 @@ class JsCalendar extends JsBase
|
|||||||
* @param bool $whole_day
|
* @param bool $whole_day
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected static function Duration($start, $end, bool $whole_day)
|
protected static function Duration($start, $end, bool $whole_day=false)
|
||||||
{
|
{
|
||||||
$start = Api\DateTime::to($start, 'object');
|
$start = Api\DateTime::to($start, 'object');
|
||||||
$end = Api\DateTime::to($end, 'object');
|
$end = Api\DateTime::to($end, 'object');
|
||||||
@ -499,6 +723,61 @@ class JsCalendar extends JsBase
|
|||||||
return $parsed;
|
return $parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return participants object of task aka Responsible
|
||||||
|
*
|
||||||
|
* We add info_owner (as owner), info_responsible (as attendee) and info_cc (as informational)
|
||||||
|
*
|
||||||
|
* @param array $entry
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function Responsible(array $entry)
|
||||||
|
{
|
||||||
|
$participants = [];
|
||||||
|
foreach(array_unique(array_merge((array)$entry['info_owner'], $entry['info_responsible'],
|
||||||
|
$entry['info_cc'] ? explode(',', $entry['info_cc']) : [])) as $uid)
|
||||||
|
{
|
||||||
|
if (is_numeric($uid))
|
||||||
|
{
|
||||||
|
$info = [
|
||||||
|
'name' => Api\Accounts::id2name($uid, 'account_fullname'),
|
||||||
|
'email' => Api\Accounts::id2name($uid, 'account_email'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (preg_match('/^(.*) <(.*)>$/', $uid, $matches))
|
||||||
|
{
|
||||||
|
$info = [
|
||||||
|
'name' => $matches[1],
|
||||||
|
'email' => $matches[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$info['email'] = $uid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$participant = array_filter([
|
||||||
|
self::AT_TYPE => self::TYPE_PARTICIPANT,
|
||||||
|
'name' => $info['name'] ?? null,
|
||||||
|
'email' => $info['email'] ?? null,
|
||||||
|
'kind' => $info['kind'] ?? 'individual',
|
||||||
|
'roles' => array_filter([
|
||||||
|
'owner' => $uid == $entry['info_owner'],
|
||||||
|
//'chair' => $role === 'CHAIR',
|
||||||
|
'attendee' => is_numeric($uid) && ($uid != $entry['info_owner'] || in_array($uid, $entry['info_responsible']??[])),
|
||||||
|
//'optional' => $role === 'OPT-PARTICIPANT',
|
||||||
|
'informational' => !is_numeric($uid), // info_cc emails
|
||||||
|
]),
|
||||||
|
'participationStatus' => null,
|
||||||
|
]);
|
||||||
|
$participants[$uid] = $participant;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $participants;
|
||||||
|
}
|
||||||
|
|
||||||
protected static function jscalRoles2role(array $roles=null, string $default_role=null)
|
protected static function jscalRoles2role(array $roles=null, string $default_role=null)
|
||||||
{
|
{
|
||||||
$role = $default_role ?? 'REQ-PARTICIPANT';
|
$role = $default_role ?? 'REQ-PARTICIPANT';
|
||||||
@ -557,7 +836,7 @@ class JsCalendar extends JsBase
|
|||||||
* @param int $priority
|
* @param int $priority
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected static function Priority(int $priority)
|
protected static function Priority(int $priority, bool $infolog=false)
|
||||||
{
|
{
|
||||||
static $priority_egw2jscal = array(
|
static $priority_egw2jscal = array(
|
||||||
0 => 0, // undefined
|
0 => 0, // undefined
|
||||||
@ -565,7 +844,13 @@ class JsCalendar extends JsBase
|
|||||||
2 => 5, // normal
|
2 => 5, // normal
|
||||||
3 => 1, // high
|
3 => 1, // high
|
||||||
);
|
);
|
||||||
return $priority_egw2jscal[$priority];
|
static $infolog_priority_2egwjscal = array(
|
||||||
|
0 => 9, // low
|
||||||
|
1 => 5, // normal
|
||||||
|
2 => 3, // high
|
||||||
|
3 => 1, // urgent
|
||||||
|
);
|
||||||
|
return $infolog ? $infolog_priority_2egwjscal[$priority] : $priority_egw2jscal[$priority];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -574,7 +859,7 @@ class JsCalendar extends JsBase
|
|||||||
* @param int $priority
|
* @param int $priority
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected static function parsePriority(int $priority)
|
protected static function parsePriority(int $priority, bool $infolog=false)
|
||||||
{
|
{
|
||||||
static $priority_jscal2egw = [
|
static $priority_jscal2egw = [
|
||||||
9 => 1, 8 => 1, 7 => 1, // low
|
9 => 1, 8 => 1, 7 => 1, // low
|
||||||
@ -582,7 +867,13 @@ class JsCalendar extends JsBase
|
|||||||
3 => 3, 2 => 3, 1 => 3, // high
|
3 => 3, 2 => 3, 1 => 3, // high
|
||||||
0 => 0, // undefined
|
0 => 0, // undefined
|
||||||
];
|
];
|
||||||
return $priority_jscal2egw[$priority] ?? throw new \InvalidArgumentException("Priority must be between 0 and 9");
|
static $infolog_priority_jscal2egw = [
|
||||||
|
9 => 0, 8 => 0, 7 => 0, // low
|
||||||
|
6 => 1, 5 => 1, 4 => 1, 0 => 1, // normal
|
||||||
|
3 => 2, 2 => 2, // high
|
||||||
|
1 => 3, // urgent
|
||||||
|
];
|
||||||
|
return ($infolog?$infolog_priority_jscal2egw:$priority_jscal2egw)[$priority] ?? throw new \InvalidArgumentException("Priority must be between 0 and 9");
|
||||||
}
|
}
|
||||||
|
|
||||||
const TYPE_RECURRENCE_RULE = 'RecurrenceRule';
|
const TYPE_RECURRENCE_RULE = 'RecurrenceRule';
|
||||||
@ -596,20 +887,25 @@ class JsCalendar extends JsBase
|
|||||||
* @param array $event
|
* @param array $event
|
||||||
* @param array $data JSCalendar representation of event to calculate overrides
|
* @param array $data JSCalendar representation of event to calculate overrides
|
||||||
* @param array $exceptions exceptions
|
* @param array $exceptions exceptions
|
||||||
|
* @param ?array $rrule array with values for keys "FREQ", "INTERVAL", "UNTIL", ...
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function Recurrence(array $event, array $data, array $exceptions=[])
|
protected static function Recurrence(array $event, array $data, array $exceptions=[], ?array $rrule=null)
|
||||||
{
|
{
|
||||||
$overrides = [];
|
$overrides = [];
|
||||||
if (!empty($event['recur_type']))
|
if (!empty($event['recur_type']) || isset($rrule))
|
||||||
|
{
|
||||||
|
if (!isset($rrule))
|
||||||
{
|
{
|
||||||
$rriter = \calendar_rrule::event2rrule($event, false);
|
$rriter = \calendar_rrule::event2rrule($event, false);
|
||||||
$rrule = $rriter->generate_rrule('2.0');
|
$rrule = $rriter->generate_rrule('2.0');
|
||||||
|
}
|
||||||
$rule = array_filter([
|
$rule = array_filter([
|
||||||
self::AT_TYPE => self::TYPE_RECURRENCE_RULE,
|
self::AT_TYPE => self::TYPE_RECURRENCE_RULE,
|
||||||
'frequency' => strtolower($rrule['FREQ']),
|
'frequency' => strtolower($rrule['FREQ']),
|
||||||
'interval' => $rrule['INTERVAL'] ?? null,
|
'interval' => $rrule['INTERVAL'] ?? null,
|
||||||
'until' => empty($rrule['UNTIL']) ? null : self::DateTime($rrule['UNTIL'], $event['tzid']),
|
'until' => empty($rrule['UNTIL']) ? null : self::DateTime($rrule['UNTIL'], $event['tzid']),
|
||||||
|
'count' => $rrule['COUNT'] ?? null ? (int)$rrule['COUNT'] : null,
|
||||||
]);
|
]);
|
||||||
if (!empty($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts']) &&
|
if (!empty($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts']) &&
|
||||||
$GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'] !== 'Monday')
|
$GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'] !== 'Monday')
|
||||||
@ -658,6 +954,44 @@ class JsCalendar extends JsBase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Infolog RRULE stored in cfs to JsCalendar RecurrenceRules
|
||||||
|
*
|
||||||
|
* @param array $cfs
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function cfRrule2recurrenceRules(array $cfs)
|
||||||
|
{
|
||||||
|
$rrule = [];
|
||||||
|
foreach(explode(';', $cfs['##RRULE']) as $pair)
|
||||||
|
{
|
||||||
|
[$name, $value] = explode('=', $pair);
|
||||||
|
$rrule[$name] = $value;
|
||||||
|
}
|
||||||
|
return self::Recurrence(['tzid' => Api\DateTime::$user_timezone->getName()], [], [], $rrule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse RecurrenceRules to InfoLog cf stored RRULE
|
||||||
|
* @param array $recurenaceRules
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function parseRecuranceRules2cfRrule(array $recurenaceRules=[])
|
||||||
|
{
|
||||||
|
$rrule = [];
|
||||||
|
foreach($recurenaceRules as $rule)
|
||||||
|
{
|
||||||
|
foreach($rule as $name => $value)
|
||||||
|
{
|
||||||
|
$rrule[] = $name.'='.$value;
|
||||||
|
}
|
||||||
|
break; // we support only one rule!
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'#RRULE' => $rrule ? implode(';', $rrule) : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get patch from an event / recurrence compared to the master event
|
* Get patch from an event / recurrence compared to the master event
|
||||||
*
|
*
|
||||||
@ -808,4 +1142,17 @@ class JsCalendar extends JsBase
|
|||||||
}
|
}
|
||||||
return $calendar_bo;
|
return $calendar_bo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \infolog_bo
|
||||||
|
*/
|
||||||
|
protected static function getInfolog()
|
||||||
|
{
|
||||||
|
static $infolog_bo=null;
|
||||||
|
if (!isset($infolog_bo))
|
||||||
|
{
|
||||||
|
$infolog_bo = new \infolog_bo();
|
||||||
|
}
|
||||||
|
return $infolog_bo;
|
||||||
|
}
|
||||||
}
|
}
|
@ -550,8 +550,9 @@ class infolog_bo
|
|||||||
* TZID timezone name e.g. 'UTC'
|
* TZID timezone name e.g. 'UTC'
|
||||||
* or NULL for timestamps in user-time
|
* or NULL for timestamps in user-time
|
||||||
* or false for timestamps in server-time
|
* or false for timestamps in server-time
|
||||||
|
* @param string $type 'ts' timestamp, 'object': DateTime objects
|
||||||
*/
|
*/
|
||||||
function time2time(&$values, $fromTZId=false, $toTZId=null)
|
function time2time(&$values, $fromTZId=false, $toTZId=null, $type='ts')
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($fromTZId === $toTZId) return;
|
if ($fromTZId === $toTZId) return;
|
||||||
@ -608,7 +609,7 @@ class infolog_bo
|
|||||||
{
|
{
|
||||||
$time->setTimezone($toTZ);
|
$time->setTimezone($toTZ);
|
||||||
}
|
}
|
||||||
$values[$key] = Api\DateTime::to($time,'ts');
|
$values[$key] = Api\DateTime::to($time, $type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//error_log(__METHOD__.'() --> values[info_enddate]='.date('Y-m-d H:i:s',$values['info_enddate']));
|
//error_log(__METHOD__.'() --> values[info_enddate]='.date('Y-m-d H:i:s',$values['info_enddate']));
|
||||||
@ -676,9 +677,9 @@ class infolog_bo
|
|||||||
$this->link_id2from($data);
|
$this->link_id2from($data);
|
||||||
}
|
}
|
||||||
// convert server- to user-time
|
// convert server- to user-time
|
||||||
if ($date_format == 'ts')
|
if ($date_format !== 'server')
|
||||||
{
|
{
|
||||||
$this->time2time($data);
|
$this->time2time($data, false, null, $date_format);
|
||||||
|
|
||||||
// pre-cache title and file access
|
// pre-cache title and file access
|
||||||
self::set_link_cache($data);
|
self::set_link_cache($data);
|
||||||
|
@ -188,7 +188,7 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
|
|
||||||
// check if we have to return the full calendar data or just the etag's
|
// check if we have to return the full calendar data or just the etag's
|
||||||
if (!($filter['calendar_data'] = $options['props'] == 'all' &&
|
if (!($filter['calendar_data'] = $options['props'] == 'all' &&
|
||||||
$options['root']['ns'] == Api\CalDAV::CALDAV) && is_array($options['props']))
|
$options['root']['ns'] == Api\CalDAV::CALDAV || isset($_GET['download'])) && is_array($options['props']))
|
||||||
{
|
{
|
||||||
foreach($options['props'] as $prop)
|
foreach($options['props'] as $prop)
|
||||||
{
|
{
|
||||||
@ -252,6 +252,7 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
{
|
{
|
||||||
if ($this->debug) $starttime = microtime(true);
|
if ($this->debug) $starttime = microtime(true);
|
||||||
|
|
||||||
|
$is_jstask = Api\CalDAV::isJSON();
|
||||||
if (($calendar_data = $filter['calendar_data']))
|
if (($calendar_data = $filter['calendar_data']))
|
||||||
{
|
{
|
||||||
$handler = self::_get_handler();
|
$handler = self::_get_handler();
|
||||||
@ -284,7 +285,7 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
'order' => $order,
|
'order' => $order,
|
||||||
'sort' => $sort,
|
'sort' => $sort,
|
||||||
'filter' => $task_filter,
|
'filter' => $task_filter,
|
||||||
'date_format' => 'server',
|
'date_format' => $is_jstask ? 'object' : 'server',
|
||||||
'col_filter' => $filter,
|
'col_filter' => $filter,
|
||||||
'custom_fields' => true, // otherwise custom fields get NOT loaded!
|
'custom_fields' => true, // otherwise custom fields get NOT loaded!
|
||||||
'start' => 0,
|
'start' => 0,
|
||||||
@ -339,10 +340,19 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
'displayname' => $task['info_subject'],
|
'displayname' => $task['info_subject'],
|
||||||
);
|
);
|
||||||
if ($calendar_data)
|
if ($calendar_data)
|
||||||
|
{
|
||||||
|
if ($is_jstask)
|
||||||
|
{
|
||||||
|
$content = Api\CalDAV\JsCalendar::JsTask($task, false);
|
||||||
|
$props['getcontentlength'] = bytes(Api\CalDAV::json_encode($content, $is_jstask));
|
||||||
|
$props['calendar-data'] = Api\CalDAV::mkprop(Api\CalDAV::CALDAV, 'calendar-data', $content);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
$content = $handler->exportVTODO($task, '2.0', null); // no METHOD:PUBLISH for CalDAV
|
$content = $handler->exportVTODO($task, '2.0', null); // no METHOD:PUBLISH for CalDAV
|
||||||
$props['getcontentlength'] = bytes($content);
|
$props['getcontentlength'] = bytes($content);
|
||||||
$props[] = Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-data',$content);
|
$props['calendar-data'] = Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-data',$content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (++$yielded && isset($nresults) && $yielded > $nresults)
|
if (++$yielded && isset($nresults) && $yielded > $nresults)
|
||||||
{
|
{
|
||||||
@ -585,8 +595,17 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
return $task;
|
return $task;
|
||||||
}
|
}
|
||||||
$handler = $this->_get_handler();
|
$handler = $this->_get_handler();
|
||||||
|
// jsTask or iCal
|
||||||
|
if (($type=Api\CalDAV::isJSON($_SERVER['HTTP_ACCEPT'])) || ($type=Api\CalDAV::isJSON()))
|
||||||
|
{
|
||||||
|
$options['data'] = Api\CalDAV\JsCalendar::JsTask($task, $type);
|
||||||
|
$options['mimetype'] = Api\CalDAV\JsCalendar::MIME_TYPE_JSTASK.';charset=utf-8';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$options['data'] = $handler->exportVTODO($task, '2.0', null); // no METHOD:PUBLISH for CalDAV
|
$options['data'] = $handler->exportVTODO($task, '2.0', null); // no METHOD:PUBLISH for CalDAV
|
||||||
$options['mimetype'] = 'text/calendar; charset=utf-8';
|
$options['mimetype'] = 'text/calendar; charset=utf-8';
|
||||||
|
}
|
||||||
header('Content-Encoding: identity');
|
header('Content-Encoding: identity');
|
||||||
header('ETag: "'.$this->get_etag($task).'"');
|
header('ETag: "'.$this->get_etag($task).'"');
|
||||||
return true;
|
return true;
|
||||||
@ -762,7 +781,8 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
*/
|
*/
|
||||||
function read($id)
|
function read($id)
|
||||||
{
|
{
|
||||||
return $this->bo->read(array(self::$path_attr => $id, "info_status!='deleted'"),false,'server');
|
return $this->bo->read(array(self::$path_attr => $id, "info_status!='deleted'"),false,
|
||||||
|
Api\CalDAV::isJson() ? 'object' : 'server');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -823,11 +843,11 @@ class infolog_groupdav extends Api\CalDAV\Handler
|
|||||||
{
|
{
|
||||||
$info = $this->bo->read($info,true,'server');
|
$info = $this->bo->read($info,true,'server');
|
||||||
}
|
}
|
||||||
if (!is_array($info) || !isset($info['info_id']) || !isset($info['info_datemodified']))
|
if (!is_array($info) || !isset($info['info_id']) || !isset($info['info_etag']) || !isset($info['info_datemodified']))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $info['info_id'].':'.$info['info_datemodified'];
|
return $info['info_id'].':'.$info['info_etag'].':'.Api\DateTime::to($info['info_datemodified'], 'ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user