3131use OCA \DAV \Events \SubscriptionCreatedEvent ;
3232use OCA \DAV \Events \SubscriptionDeletedEvent ;
3333use OCA \DAV \Events \SubscriptionUpdatedEvent ;
34+ use OCA \DAV \Exception \UidConflict ;
3435use OCP \AppFramework \Db \TTransactional ;
3536use OCP \Calendar \CalendarExportOptions ;
3637use OCP \Calendar \Events \CalendarObjectCreatedEvent ;
@@ -1488,6 +1489,35 @@ public function getMultipleCalendarObjects($calendarId, array $uris, $calendarTy
14881489 return $ objects ;
14891490 }
14901491
1492+ /**
1493+ * Find an existing calendar object that already carries the given UID in a calendar collection
1494+ *
1495+ * @param int $calendarId
1496+ * @param string $uid
1497+ * @param int $calendarType
1498+ * @param bool|null $deleted Whether to match trashed objects: false for live objects only, true for trashed only, null for any
1499+ * @return array|null The existing object, or null when no match is found
1500+ */
1501+ public function findCalendarObjectByUid (int $ calendarId , string $ uid , int $ calendarType = self ::CALENDAR_TYPE_CALENDAR , ?bool $ deleted = false ): ?array {
1502+ $ qb = $ this ->db ->getQueryBuilder ();
1503+ $ qb ->select ('* ' )
1504+ ->from ('calendarobjects ' )
1505+ ->where ($ qb ->expr ()->eq ('calendarid ' , $ qb ->createNamedParameter ($ calendarId , IQueryBuilder::PARAM_INT )))
1506+ ->andWhere ($ qb ->expr ()->eq ('uid ' , $ qb ->createNamedParameter ($ uid , IQueryBuilder::PARAM_STR )))
1507+ ->andWhere ($ qb ->expr ()->eq ('calendartype ' , $ qb ->createNamedParameter ($ calendarType , IQueryBuilder::PARAM_INT )))
1508+ ->setMaxResults (1 );
1509+ if ($ deleted === false ) {
1510+ $ qb ->andWhere ($ qb ->expr ()->isNull ('deleted_at ' ));
1511+ } elseif ($ deleted === true ) {
1512+ $ qb ->andWhere ($ qb ->expr ()->isNotNull ('deleted_at ' ));
1513+ }
1514+ $ result = $ qb ->executeQuery ();
1515+ $ row = $ result ->fetch ();
1516+ $ result ->closeCursor ();
1517+
1518+ return $ row === false ? null : $ this ->rowToCalendarObject ($ row );
1519+ }
1520+
14911521 /**
14921522 * Creates a new calendar object.
14931523 *
@@ -1512,35 +1542,15 @@ public function createCalendarObject($calendarId, $objectUri, $calendarData, $ca
15121542 $ extraData = $ this ->getDenormalizedData ($ calendarData );
15131543
15141544 return $ this ->atomic (function () use ($ calendarId , $ objectUri , $ calendarData , $ extraData , $ calendarType ) {
1515- // Try to detect duplicates
1516- $ qb = $ this ->db ->getQueryBuilder ();
1517- $ qb ->select ($ qb ->func ()->count ('* ' ))
1518- ->from ('calendarobjects ' )
1519- ->where ($ qb ->expr ()->eq ('calendarid ' , $ qb ->createNamedParameter ($ calendarId )))
1520- ->andWhere ($ qb ->expr ()->eq ('uid ' , $ qb ->createNamedParameter ($ extraData ['uid ' ])))
1521- ->andWhere ($ qb ->expr ()->eq ('calendartype ' , $ qb ->createNamedParameter ($ calendarType )))
1522- ->andWhere ($ qb ->expr ()->isNull ('deleted_at ' ));
1523- $ result = $ qb ->executeQuery ();
1524- $ count = (int )$ result ->fetchOne ();
1525- $ result ->closeCursor ();
1526-
1527- if ($ count !== 0 ) {
1528- throw new BadRequest ('Calendar object with uid already exists in this calendar collection. ' );
1529- }
1530- // For a more specific error message we also try to explicitly look up the UID but as a deleted entry
1531- $ qbDel = $ this ->db ->getQueryBuilder ();
1532- $ qbDel ->select ('* ' )
1533- ->from ('calendarobjects ' )
1534- ->where ($ qbDel ->expr ()->eq ('calendarid ' , $ qbDel ->createNamedParameter ($ calendarId )))
1535- ->andWhere ($ qbDel ->expr ()->eq ('uid ' , $ qbDel ->createNamedParameter ($ extraData ['uid ' ])))
1536- ->andWhere ($ qbDel ->expr ()->eq ('calendartype ' , $ qbDel ->createNamedParameter ($ calendarType )))
1537- ->andWhere ($ qbDel ->expr ()->isNotNull ('deleted_at ' ));
1538- $ result = $ qbDel ->executeQuery ();
1539- $ found = $ result ->fetch ();
1540- $ result ->closeCursor ();
1541- if ($ found !== false ) {
1542- // the object existed previously but has been deleted
1543- // remove the trashbin entry and continue as if it was a new object
1545+ // Try to detect duplicate uids in the target collection
1546+ $ existing = $ this ->findCalendarObjectByUid ($ calendarId , $ extraData ['uid ' ], $ calendarType );
1547+ if ($ existing !== null ) {
1548+ // RFC 4791 no-uid-conflict (409) reporting the existing object's href.
1549+ throw UidConflict::forCalendar ($ existing ['uri ' ]);
1550+ }
1551+ // The UID may still belong to a trashed object; delete it and replace it with the new object.
1552+ $ found = $ this ->findCalendarObjectByUid ($ calendarId , $ extraData ['uid ' ], $ calendarType , true );
1553+ if ($ found !== null ) {
15441554 $ this ->deleteCalendarObject ($ calendarId , $ found ['uri ' ], $ calendarType , true );
15451555 }
15461556
@@ -1670,6 +1680,13 @@ public function moveCalendarObject(string $sourcePrincipalUri, int $sourceObject
16701680
16711681 $ sourceCalendarId = $ object ['calendarid ' ];
16721682 $ sourceObjectUri = $ object ['uri ' ];
1683+ $ sourceObjectUid = $ object ['uid ' ];
1684+
1685+ // Try to detect duplicate uids in the target collection
1686+ $ existing = $ this ->findCalendarObjectByUid ($ targetCalendarId , $ sourceObjectUid , $ calendarType );
1687+ if ($ existing !== null ) {
1688+ throw UidConflict::forCalendar ($ existing ['uri ' ]);
1689+ }
16731690
16741691 $ query = $ this ->db ->getQueryBuilder ();
16751692 $ query ->update ('calendarobjects ' )
@@ -2620,7 +2637,7 @@ public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null)
26202637
26212638 public function getCalendarObjectById (string $ principalUri , int $ id ): ?array {
26222639 $ query = $ this ->db ->getQueryBuilder ();
2623- $ query ->select (['co.id ' , 'co.uri ' , 'co.lastmodified ' , 'co.etag ' , 'co.calendarid ' , 'co.size ' , 'co.calendardata ' , 'co.componenttype ' , 'co.classification ' , 'co.deleted_at ' ])
2640+ $ query ->select (['co.id ' , 'co.uri ' , 'co.uid ' , ' co. lastmodified ' , 'co.etag ' , 'co.calendarid ' , 'co.size ' , 'co.calendardata ' , 'co.componenttype ' , 'co.classification ' , 'co.deleted_at ' ])
26242641 ->selectAlias ('c.uri ' , 'calendaruri ' )
26252642 ->from ('calendarobjects ' , 'co ' )
26262643 ->join ('co ' , 'calendars ' , 'c ' , $ query ->expr ()->eq ('c.id ' , 'co.calendarid ' , IQueryBuilder::PARAM_INT ))
@@ -2637,6 +2654,7 @@ public function getCalendarObjectById(string $principalUri, int $id): ?array {
26372654 return [
26382655 'id ' => $ row ['id ' ],
26392656 'uri ' => $ row ['uri ' ],
2657+ 'uid ' => $ row ['uid ' ],
26402658 'lastmodified ' => $ row ['lastmodified ' ],
26412659 'etag ' => '" ' . $ row ['etag ' ] . '" ' ,
26422660 'calendarid ' => $ row ['calendarid ' ],
0 commit comments