3232use OCA \DAV \Events \SubscriptionCreatedEvent ;
3333use OCA \DAV \Events \SubscriptionDeletedEvent ;
3434use OCA \DAV \Events \SubscriptionUpdatedEvent ;
35+ use OCA \DAV \Exception \UidConflict ;
3536use OCP \AppFramework \Db \TTransactional ;
3637use OCP \Calendar \CalendarExportOptions ;
3738use OCP \Calendar \Events \CalendarObjectCreatedEvent ;
@@ -1498,6 +1499,35 @@ public function getMultipleCalendarObjects($calendarId, array $uris, $calendarTy
14981499 return $ objects ;
14991500 }
15001501
1502+ /**
1503+ * Find an existing calendar object that already carries the given UID in a calendar collection
1504+ *
1505+ * @param int $calendarId
1506+ * @param string $uid
1507+ * @param int $calendarType
1508+ * @param bool|null $deleted Whether to match trashed objects: false for live objects only, true for trashed only, null for any
1509+ * @return array|null The existing object, or null when no match is found
1510+ */
1511+ public function findCalendarObjectByUid (int $ calendarId , string $ uid , int $ calendarType = self ::CALENDAR_TYPE_CALENDAR , ?bool $ deleted = false ): ?array {
1512+ $ qb = $ this ->db ->getQueryBuilder ();
1513+ $ qb ->select ('* ' )
1514+ ->from ('calendarobjects ' )
1515+ ->where ($ qb ->expr ()->eq ('calendarid ' , $ qb ->createNamedParameter ($ calendarId , IQueryBuilder::PARAM_INT )))
1516+ ->andWhere ($ qb ->expr ()->eq ('uid ' , $ qb ->createNamedParameter ($ uid , IQueryBuilder::PARAM_STR )))
1517+ ->andWhere ($ qb ->expr ()->eq ('calendartype ' , $ qb ->createNamedParameter ($ calendarType , IQueryBuilder::PARAM_INT )))
1518+ ->setMaxResults (1 );
1519+ if ($ deleted === false ) {
1520+ $ qb ->andWhere ($ qb ->expr ()->isNull ('deleted_at ' ));
1521+ } elseif ($ deleted === true ) {
1522+ $ qb ->andWhere ($ qb ->expr ()->isNotNull ('deleted_at ' ));
1523+ }
1524+ $ result = $ qb ->executeQuery ();
1525+ $ row = $ result ->fetchAssociative ();
1526+ $ result ->closeCursor ();
1527+
1528+ return $ row === false ? null : $ this ->rowToCalendarObject ($ row );
1529+ }
1530+
15011531 /**
15021532 * Creates a new calendar object.
15031533 *
@@ -1523,35 +1553,15 @@ public function createCalendarObject($calendarId, $objectUri, $calendarData, $ca
15231553 $ extraData = $ this ->getDenormalizedData ($ calendarData );
15241554
15251555 return $ this ->atomic (function () use ($ calendarId , $ objectUri , $ calendarData , $ extraData , $ calendarType ) {
1526- // Try to detect duplicates
1527- $ qb = $ this ->db ->getQueryBuilder ();
1528- $ qb ->select ($ qb ->func ()->count ('* ' ))
1529- ->from ('calendarobjects ' )
1530- ->where ($ qb ->expr ()->eq ('calendarid ' , $ qb ->createNamedParameter ($ calendarId )))
1531- ->andWhere ($ qb ->expr ()->eq ('uid ' , $ qb ->createNamedParameter ($ extraData ['uid ' ])))
1532- ->andWhere ($ qb ->expr ()->eq ('calendartype ' , $ qb ->createNamedParameter ($ calendarType )))
1533- ->andWhere ($ qb ->expr ()->isNull ('deleted_at ' ));
1534- $ result = $ qb ->executeQuery ();
1535- $ count = (int )$ result ->fetchOne ();
1536- $ result ->closeCursor ();
1537-
1538- if ($ count !== 0 ) {
1539- throw new BadRequest ('Calendar object with uid already exists in this calendar collection. ' );
1540- }
1541- // For a more specific error message we also try to explicitly look up the UID but as a deleted entry
1542- $ qbDel = $ this ->db ->getQueryBuilder ();
1543- $ qbDel ->select ('* ' )
1544- ->from ('calendarobjects ' )
1545- ->where ($ qbDel ->expr ()->eq ('calendarid ' , $ qbDel ->createNamedParameter ($ calendarId )))
1546- ->andWhere ($ qbDel ->expr ()->eq ('uid ' , $ qbDel ->createNamedParameter ($ extraData ['uid ' ])))
1547- ->andWhere ($ qbDel ->expr ()->eq ('calendartype ' , $ qbDel ->createNamedParameter ($ calendarType )))
1548- ->andWhere ($ qbDel ->expr ()->isNotNull ('deleted_at ' ));
1549- $ result = $ qbDel ->executeQuery ();
1550- $ found = $ result ->fetchAssociative ();
1551- $ result ->closeCursor ();
1552- if ($ found !== false ) {
1553- // the object existed previously but has been deleted
1554- // remove the trashbin entry and continue as if it was a new object
1556+ // Try to detect duplicate uids in the target collection
1557+ $ existing = $ this ->findCalendarObjectByUid ($ calendarId , $ extraData ['uid ' ], $ calendarType );
1558+ if ($ existing !== null ) {
1559+ // RFC 4791 no-uid-conflict (409) reporting the existing object's href.
1560+ throw UidConflict::forCalendar ($ existing ['uri ' ]);
1561+ }
1562+ // The UID may still belong to a trashed object; delete it and replace it with the new object.
1563+ $ found = $ this ->findCalendarObjectByUid ($ calendarId , $ extraData ['uid ' ], $ calendarType , true );
1564+ if ($ found !== null ) {
15551565 $ this ->deleteCalendarObject ($ calendarId , $ found ['uri ' ], $ calendarType , true );
15561566 }
15571567
@@ -1702,6 +1712,13 @@ public function moveCalendarObject(string $sourcePrincipalUri, int $sourceObject
17021712
17031713 $ sourceCalendarId = $ object ['calendarid ' ];
17041714 $ sourceObjectUri = $ object ['uri ' ];
1715+ $ sourceObjectUid = $ object ['uid ' ];
1716+
1717+ // Try to detect duplicate uids in the target collection
1718+ $ existing = $ this ->findCalendarObjectByUid ($ targetCalendarId , $ sourceObjectUid , $ calendarType );
1719+ if ($ existing !== null ) {
1720+ throw UidConflict::forCalendar ($ existing ['uri ' ]);
1721+ }
17051722
17061723 $ query = $ this ->db ->getQueryBuilder ();
17071724 $ query ->update ('calendarobjects ' )
@@ -2659,7 +2676,7 @@ public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null)
26592676
26602677 public function getCalendarObjectById (string $ principalUri , int $ id ): ?array {
26612678 $ query = $ this ->db ->getQueryBuilder ();
2662- $ query ->select (['co.id ' , 'co.uri ' , 'co.lastmodified ' , 'co.etag ' , 'co.calendarid ' , 'co.size ' , 'co.calendardata ' , 'co.componenttype ' , 'co.classification ' , 'co.deleted_at ' ])
2679+ $ 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 ' ])
26632680 ->selectAlias ('c.uri ' , 'calendaruri ' )
26642681 ->from ('calendarobjects ' , 'co ' )
26652682 ->join ('co ' , 'calendars ' , 'c ' , $ query ->expr ()->eq ('c.id ' , 'co.calendarid ' , IQueryBuilder::PARAM_INT ))
@@ -2676,6 +2693,7 @@ public function getCalendarObjectById(string $principalUri, int $id): ?array {
26762693 return [
26772694 'id ' => $ row ['id ' ],
26782695 'uri ' => $ row ['uri ' ],
2696+ 'uid ' => $ row ['uid ' ],
26792697 'lastmodified ' => $ row ['lastmodified ' ],
26802698 'etag ' => '" ' . $ row ['etag ' ] . '" ' ,
26812699 'calendarid ' => $ row ['calendarid ' ],
0 commit comments