nextcloud / server

☁️ Nextcloud server, a safe home for all your data
https://nextcloud.com
GNU Affero General Public License v3.0
26.67k stars 4k forks source link

CalDavBackend.search does not handle VTODO's properly #45333

Open kesselb opened 4 months ago

kesselb commented 4 months ago

Follow-up for https://github.com/nextcloud/server/pull/44752 and https://github.com/nextcloud/server/pull/45222

The "Upcoming events" widget (provided by the calendar app) is using ICalendar.search / CalDavBackend.search to obtain a list of calendar objects within a given time range since calendar 4.6.0. Former versions sent one xhr request per calendar to the CalDAV api to obtain the calendar objects.

A user also mentioned that tasks (e.g. from the tasks app) were shown in the "Upcoming events" widget with older version and now are missing^1.

I had a brief look with the tasks app and found a couple of things:

"Upcoming events" use a search with a time range. That's done by filtering on oc_calendarobjects.firstoccurence and oc_calendarobjects.lastoccurence and additional processing. However, event's from the tasks app with a start and end date still ended up in oc_calendarobjects with firstoccurrence = null and lastoccurence = null.

Proof of concept to bring back tasks in the "Upcoming events" widget.


Index: apps/dav/lib/CalDAV/CalDavBackend.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php
--- a/apps/dav/lib/CalDAV/CalDavBackend.php (revision d8189bf775459ca87b654e5eda6eafc86bcdd0a8)
+++ b/apps/dav/lib/CalDAV/CalDavBackend.php (date 1715795614049)
@@ -2011,6 +2011,7 @@

            // Expand recurrences if an explicit time range is requested
            if ($calendarData instanceof VCalendar
+               && isset($calendarData->VEVENT)
                && isset($options['timerange']['start'], $options['timerange']['end'])) {
                $calendarData = $calendarData->expand(
                    $options['timerange']['start'],
@@ -2072,7 +2073,7 @@
                'name' => 'VCALENDAR',
                'comp-filters' => [
                    [
-                       'name' => 'VEVENT',
+                       'name' => $row['componenttype'],
                        'comp-filters' => [],
                        'prop-filters' => [],
                        'is-not-defined' => false,
@@ -2946,7 +2947,7 @@
        foreach ($vObject->getComponents() as $component) {
            if ($component->name !== 'VTIMEZONE') {
                // Finding all VEVENTs, and track them
-               if ($component->name === 'VEVENT') {
+               if ($component->name === 'VEVENT' || $component->name === 'VTODO') {
                    $vEvents[] = $component;
                    if ($component->DTSTART) {
                        $hasDTSTART = true;
@@ -2975,6 +2976,8 @@
                    $endDate = clone $component->DTSTART->getDateTime();
                    $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
                    $lastOccurrence = $endDate->getTimeStamp();
+               } elseif (isset($component->DUE)) {
+                   $lastOccurrence = $component->DUE->getDateTime()->getTimeStamp();
                } elseif (!$component->DTSTART->hasTime()) {
                    $endDate = clone $component->DTSTART->getDateTime();
                    $endDate->modify('+1 day');

Alternative for $isValid = $this->validateFilterForObject

            $vObject = Reader::read($row['calendardata']);
            $isValid = false;

            if (isset($vObject->VEVENT)) {
                $isValid = $vObject->VEVENT->isInTimeRange($start, $end);
            } else if (isset($vObject->VTODO)) {
                $isValid = $vObject->VTODO->isInTimeRange($start, $end);
            }
kesselb commented 4 months ago

@szaimen the backend parts are missing / broken down to 26. Should I use a different label then?