99namespace OCA \DAV \DAV ;
1010
1111use Exception ;
12+ use OCA \DAV \CalDAV \CalDavBackend ;
1213use OCA \DAV \CalDAV \Calendar ;
14+ use OCA \DAV \CalDAV \CalendarHome ;
1315use OCA \DAV \CalDAV \CalendarObject ;
1416use OCA \DAV \CalDAV \DefaultCalendarValidator ;
17+ use OCA \DAV \CalDAV \Integration \ExternalCalendar ;
18+ use OCA \DAV \CalDAV \Outbox ;
19+ use OCA \DAV \CalDAV \Trashbin \TrashbinHome ;
1520use OCA \DAV \Connector \Sabre \Directory ;
21+ use OCA \DAV \Db \PropertyMapper ;
1622use OCP \DB \QueryBuilder \IQueryBuilder ;
1723use OCP \IDBConnection ;
1824use OCP \IUser ;
25+ use Sabre \CalDAV \Schedule \Inbox ;
1926use Sabre \DAV \Exception as DavException ;
2027use Sabre \DAV \PropertyStorage \Backend \BackendInterface ;
2128use Sabre \DAV \PropFind ;
@@ -98,10 +105,9 @@ class CustomPropertiesBackend implements BackendInterface {
98105
99106 /**
100107 * Properties cache
101- *
102- * @var array
103108 */
104- private $ userCache = [];
109+ private array $ userCache = [];
110+ private array $ publishedCache = [];
105111 private XmlService $ xmlService ;
106112
107113 /**
@@ -114,6 +120,7 @@ public function __construct(
114120 private Tree $ tree ,
115121 private IDBConnection $ connection ,
116122 private IUser $ user ,
123+ private PropertyMapper $ propertyMapper ,
117124 private DefaultCalendarValidator $ defaultCalendarValidator ,
118125 ) {
119126 $ this ->xmlService = new XmlService ();
@@ -197,6 +204,13 @@ public function propFind($path, PropFind $propFind) {
197204 $ this ->cacheDirectory ($ path , $ node );
198205 }
199206
207+ if ($ node instanceof CalendarHome && $ propFind ->getDepth () !== 0 ) {
208+ $ backend = $ node ->getCalDAVBackend ();
209+ if ($ backend instanceof CalDavBackend) {
210+ $ this ->cacheCalendars ($ node , $ requestedProps );
211+ }
212+ }
213+
200214 if ($ node instanceof CalendarObject) {
201215 // No custom properties supported on individual events
202216 return ;
@@ -316,6 +330,10 @@ private function getPublishedProperties(string $path, array $requestedProperties
316330 return [];
317331 }
318332
333+ if (isset ($ this ->publishedCache [$ path ])) {
334+ return $ this ->publishedCache [$ path ];
335+ }
336+
319337 $ qb = $ this ->connection ->getQueryBuilder ();
320338 $ qb ->select ('* ' )
321339 ->from (self ::TABLE_NAME )
@@ -326,6 +344,7 @@ private function getPublishedProperties(string $path, array $requestedProperties
326344 $ props [$ row ['propertyname ' ]] = $ this ->decodeValueFromDatabase ($ row ['propertyvalue ' ], $ row ['valuetype ' ]);
327345 }
328346 $ result ->closeCursor ();
347+ $ this ->publishedCache [$ path ] = $ props ;
329348 return $ props ;
330349 }
331350
@@ -364,6 +383,62 @@ private function cacheDirectory(string $path, Directory $node): void {
364383 $ this ->userCache = array_merge ($ this ->userCache , $ propsByPath );
365384 }
366385
386+ private function cacheCalendars (CalendarHome $ node , array $ requestedProperties ): void {
387+ $ calendars = $ node ->getChildren ();
388+
389+ $ users = [];
390+ foreach ($ calendars as $ calendar ) {
391+ if ($ calendar instanceof Calendar) {
392+ $ user = str_replace ('principals/users/ ' , '' , $ calendar ->getPrincipalURI ());
393+ if (!isset ($ users [$ user ])) {
394+ $ users [$ user ] = ['calendars/ ' . $ user ];
395+ }
396+ $ users [$ user ][] = 'calendars/ ' . $ user . '/ ' . $ calendar ->getUri ();
397+ } elseif ($ calendar instanceof Inbox || $ calendar instanceof Outbox || $ calendar instanceof TrashbinHome || $ calendar instanceof ExternalCalendar) {
398+ if ($ calendar ->getOwner ()) {
399+ $ user = str_replace ('principals/users/ ' , '' , $ calendar ->getOwner ());
400+ if (!isset ($ users [$ user ])) {
401+ $ users [$ user ] = ['calendars/ ' . $ user ];
402+ }
403+ $ users [$ user ][] = 'calendars/ ' . $ user . '/ ' . $ calendar ->getName ();
404+ }
405+ }
406+ }
407+
408+ // user properties
409+ $ properties = $ this ->propertyMapper ->findPropertiesByPathsAndUsers ($ users );
410+
411+ $ propsByPath = [];
412+ foreach ($ users as $ paths ) {
413+ foreach ($ paths as $ path ) {
414+ $ propsByPath [$ path ] = [];
415+ }
416+ }
417+
418+ foreach ($ properties as $ property ) {
419+ $ propsByPath [$ property ->getPropertypath ()][$ property ->getPropertyname ()] = $ this ->decodeValueFromDatabase ($ property ->getPropertyvalue (), $ property ->getValuetype ());
420+ }
421+ $ this ->userCache = array_merge ($ this ->userCache , $ propsByPath );
422+
423+ // published properties
424+ $ allowedProps = array_intersect (self ::PUBLISHED_READ_ONLY_PROPERTIES , $ requestedProperties );
425+ if (empty ($ allowedProps )) {
426+ return ;
427+ }
428+ $ paths = [];
429+ foreach ($ users as $ nestedPaths ) {
430+ $ paths = array_merge ($ paths , $ nestedPaths );
431+ }
432+ $ paths = array_unique ($ paths );
433+
434+ $ propsByPath = array_fill_keys (array_values ($ paths ), []);
435+ $ properties = $ this ->propertyMapper ->findPropertiesByPaths ($ paths , $ allowedProps );
436+ foreach ($ properties as $ property ) {
437+ $ propsByPath [$ property ->getPropertypath ()][$ property ->getPropertyname ()] = $ this ->decodeValueFromDatabase ($ property ->getPropertyvalue (), $ property ->getValuetype ());
438+ }
439+ $ this ->publishedCache = array_merge ($ this ->publishedCache , $ propsByPath );
440+ }
441+
367442 /**
368443 * Returns a list of properties for the given path and current user
369444 *
0 commit comments