Skip to content

CalDavBackend.search does not handle VTODO's properly

Open

Description

Follow-up for #44752 and #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 missing1.

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.

  • The processing for DTSTART in CalDavBackend.getDenormalizedData is only done for VEVENTS2.
  • VTODO don't have DTEND but DUE or DURATION3. We can handle DURATION, but DUE is missing4.
  • VTODO can omit DTSTART if DUE is given3.
  • CalDavBackend.search uses a comp-filter to check if the given event is within the time range. The filter for component type "name" is mandatory and set to VEVENT and will remove VTODO or VJOURNAL. The component type is already known (from the db result) and can be reused or drop the filter and ask the components directly with isInTimeRange5.
  • CalDavBackend.search uses VCalendar.expand to return the event for the given date and without recurrences. The implemention does not take VTODO in account and therefore they are removed / not added to the expanded result6.
  • 458b1cb will sort the objects by the start date, for VTODOs without start date have to figure something out, likely the start date should be today then.

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);
			}

Footnotes

  1. https://github.com/nextcloud/calendar/issues/5563#issuecomment-1900129501

  2. https://github.com/nextcloud/server/blob/b24ac6bc714e970cb8dd2f1723a030c2b913368b/apps/dav/lib/CalDAV/CalDavBackend.php#L2878-L2883

  3. https://icalendar.org/iCalendar-RFC-5545/3-6-2-to-do-component.html 2

  4. https://github.com/nextcloud/server/blob/b24ac6bc714e970cb8dd2f1723a030c2b913368b/apps/dav/lib/CalDAV/CalDavBackend.php#L2901-L2913

  5. https://github.com/nextcloud/server/blob/b24ac6bc714e970cb8dd2f1723a030c2b913368b/apps/dav/lib/CalDAV/CalDavBackend.php#L1968-L1985

  6. https://github.com/sabre-io/vobject/blob/a865996a677fd31ad06ca995e538163a89048594/lib/Component/VCalendar.php#L307-L324

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions