Calendar Sync V2
This commit is contained in:
@@ -4,24 +4,21 @@ namespace dokuwiki\plugin\luxtools;
|
||||
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VEvent;
|
||||
use Sabre\VObject\Component\VTodo;
|
||||
use Sabre\VObject\Reader;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Write-back support for local ICS files.
|
||||
*
|
||||
* Handles updating event/task status (completion, reopening) in local
|
||||
* ICS files while preserving the original component type and other properties.
|
||||
* Handles updating event status (completion, reopening) in local
|
||||
* ICS files while preserving other properties.
|
||||
*/
|
||||
class IcsWriter
|
||||
{
|
||||
/**
|
||||
* Update the STATUS of an event or task occurrence in a local ICS file.
|
||||
* Update the STATUS of an event occurrence in a local ICS file.
|
||||
*
|
||||
* For VEVENT: sets STATUS to the given value (TODO or COMPLETED).
|
||||
* For VTODO: sets STATUS and, when completing, sets COMPLETED timestamp;
|
||||
* when reopening, removes the COMPLETED property.
|
||||
* Sets STATUS to the given value (TODO or COMPLETED).
|
||||
*
|
||||
* For recurring events, this writes an override/exception for the specific
|
||||
* occurrence rather than modifying the master event.
|
||||
@@ -108,15 +105,6 @@ class IcsWriter
|
||||
}
|
||||
}
|
||||
|
||||
// Try VTODO
|
||||
foreach ($calendar->select('VTODO') as $component) {
|
||||
if (!($component instanceof VTodo)) continue;
|
||||
if (self::matchesComponent($component, $uid, $recurrenceId, $dateIso)) {
|
||||
self::setVTodoStatus($component, $newStatus);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// For recurring events without a matching override, create one
|
||||
foreach ($calendar->select('VEVENT') as $component) {
|
||||
if (!($component instanceof VEvent)) continue;
|
||||
@@ -131,25 +119,13 @@ class IcsWriter
|
||||
if ($override !== null) return true;
|
||||
}
|
||||
|
||||
// Same for VTODO with RRULE
|
||||
foreach ($calendar->select('VTODO') as $component) {
|
||||
if (!($component instanceof VTodo)) continue;
|
||||
$componentUid = trim((string)($component->UID ?? ''));
|
||||
if ($componentUid !== $uid) continue;
|
||||
|
||||
if (!isset($component->RRULE)) continue;
|
||||
|
||||
$override = self::createVTodoOccurrenceOverride($calendar, $component, $newStatus, $dateIso);
|
||||
if ($override !== null) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a component matches the given UID and recurrence criteria.
|
||||
*
|
||||
* @param VEvent|VTodo $component
|
||||
* @param VEvent $component
|
||||
* @param string $uid
|
||||
* @param string $recurrenceId
|
||||
* @param string $dateIso
|
||||
@@ -193,33 +169,6 @@ class IcsWriter
|
||||
$vevent->STATUS = $newStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the STATUS property on a VTODO with native completion semantics.
|
||||
*
|
||||
* @param VTodo $vtodo
|
||||
* @param string $newStatus
|
||||
*/
|
||||
protected static function setVTodoStatus(VTodo $vtodo, string $newStatus): void
|
||||
{
|
||||
if ($newStatus === 'COMPLETED') {
|
||||
$vtodo->STATUS = 'COMPLETED';
|
||||
// Set COMPLETED timestamp per RFC 5545
|
||||
$vtodo->COMPLETED = gmdate('Ymd\THis\Z');
|
||||
// Set PERCENT-COMPLETE to 100
|
||||
$vtodo->{'PERCENT-COMPLETE'} = '100';
|
||||
} else {
|
||||
// Reopening
|
||||
$vtodo->STATUS = 'NEEDS-ACTION';
|
||||
// Remove COMPLETED timestamp
|
||||
if (isset($vtodo->COMPLETED)) {
|
||||
unset($vtodo->COMPLETED);
|
||||
}
|
||||
if (isset($vtodo->{'PERCENT-COMPLETE'})) {
|
||||
unset($vtodo->{'PERCENT-COMPLETE'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an occurrence override for a recurring VEVENT.
|
||||
*
|
||||
@@ -300,97 +249,6 @@ class IcsWriter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an occurrence override for a recurring VTODO.
|
||||
*
|
||||
* @param VCalendar $calendar
|
||||
* @param VTodo $master
|
||||
* @param string $newStatus
|
||||
* @param string $dateIso
|
||||
* @return VTodo|null
|
||||
*/
|
||||
protected static function createVTodoOccurrenceOverride(
|
||||
VCalendar $calendar,
|
||||
VTodo $master,
|
||||
string $newStatus,
|
||||
string $dateIso
|
||||
): ?VTodo {
|
||||
try {
|
||||
$dateProperty = $master->DUE ?? $master->DTSTART ?? null;
|
||||
$isAllDay = $dateProperty !== null && strtoupper((string)($dateProperty['VALUE'] ?? '')) === 'DATE';
|
||||
|
||||
$props = [
|
||||
'UID' => (string)$master->UID,
|
||||
'SUMMARY' => (string)($master->SUMMARY ?? ''),
|
||||
];
|
||||
|
||||
if ($isAllDay) {
|
||||
$recurrenceValue = str_replace('-', '', $dateIso);
|
||||
if (isset($master->DTSTART)) {
|
||||
$props['DTSTART'] = $recurrenceValue;
|
||||
}
|
||||
if (isset($master->DUE)) {
|
||||
$props['DUE'] = $recurrenceValue;
|
||||
}
|
||||
$props['RECURRENCE-ID'] = $recurrenceValue;
|
||||
|
||||
$override = $calendar->add('VTODO', $props);
|
||||
|
||||
if (isset($override->DTSTART)) {
|
||||
$override->DTSTART['VALUE'] = 'DATE';
|
||||
}
|
||||
if (isset($override->DUE)) {
|
||||
$override->DUE['VALUE'] = 'DATE';
|
||||
}
|
||||
$override->{'RECURRENCE-ID'}['VALUE'] = 'DATE';
|
||||
} else {
|
||||
$dt = $dateProperty->getDateTime();
|
||||
$recurrenceValue = $dateIso . 'T' . $dt->format('His');
|
||||
$tz = $dt->getTimezone();
|
||||
if ($tz && $tz->getName() !== 'UTC') {
|
||||
if (isset($master->DTSTART)) {
|
||||
$props['DTSTART'] = $recurrenceValue;
|
||||
}
|
||||
if (isset($master->DUE)) {
|
||||
$props['DUE'] = $recurrenceValue;
|
||||
}
|
||||
$props['RECURRENCE-ID'] = $recurrenceValue;
|
||||
$override = $calendar->add('VTODO', $props);
|
||||
if (isset($override->DTSTART)) {
|
||||
$override->DTSTART['TZID'] = $tz->getName();
|
||||
}
|
||||
if (isset($override->DUE)) {
|
||||
$override->DUE['TZID'] = $tz->getName();
|
||||
}
|
||||
$override->{'RECURRENCE-ID'}['TZID'] = $tz->getName();
|
||||
} else {
|
||||
$recurrenceValue .= 'Z';
|
||||
if (isset($master->DTSTART)) {
|
||||
$props['DTSTART'] = $recurrenceValue;
|
||||
}
|
||||
if (isset($master->DUE)) {
|
||||
$props['DUE'] = $recurrenceValue;
|
||||
}
|
||||
$props['RECURRENCE-ID'] = $recurrenceValue;
|
||||
$override = $calendar->add('VTODO', $props);
|
||||
}
|
||||
}
|
||||
|
||||
self::setVTodoStatus($override, $newStatus);
|
||||
|
||||
if (isset($master->LOCATION)) {
|
||||
$override->LOCATION = (string)$master->LOCATION;
|
||||
}
|
||||
if (isset($master->DESCRIPTION)) {
|
||||
$override->DESCRIPTION = (string)$master->DESCRIPTION;
|
||||
}
|
||||
|
||||
return $override;
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public atomic file write for use by CalDavClient sync.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user