Skip to content

Commit 8f1393f

Browse files
committed
fix(dav): prevent trailing deletes on stack moves and restore via createFile
Signed-off-by: Jaggob <37583151+Jaggob@users.noreply.github.com>
1 parent c9333b7 commit 8f1393f

File tree

4 files changed

+65
-8
lines changed

4 files changed

+65
-8
lines changed

lib/DAV/Calendar.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ protected function validateFilterForObject($object, array $filters) {
116116

117117
public function createFile($name, $data = null) {
118118
try {
119-
$this->getChildNode($name, false)->put((string)$data);
119+
$this->getChildNode($name, false, false)->put((string)$data);
120120
$this->children = null;
121121
return;
122122
} catch (NotFound $e) {
@@ -135,10 +135,10 @@ public function createFile($name, $data = null) {
135135
}
136136

137137
public function getChild($name) {
138-
return $this->getChildNode($name, true);
138+
return $this->getChildNode($name, true, true);
139139
}
140140

141-
private function getChildNode(string $name, bool $allowPlaceholder) {
141+
private function getChildNode(string $name, bool $allowPlaceholder, bool $includeDeletedFallback) {
142142
foreach ($this->getBackendChildren() as $item) {
143143
$canonicalName = $item->getCalendarPrefix() . '-' . $item->getId() . '.ics';
144144
if ($this->isMatchingCalendarObjectName($name, $canonicalName)) {
@@ -151,7 +151,8 @@ private function getChildNode(string $name, bool $allowPlaceholder) {
151151
$fallbackItem = $this->backend->findCalendarObjectByName(
152152
$name,
153153
$this->board->getId(),
154-
$this->stack?->getId()
154+
$this->stack?->getId(),
155+
$includeDeletedFallback
155156
);
156157
if ($fallbackItem !== null) {
157158
$canonicalName = $fallbackItem->getCalendarPrefix() . '-' . $fallbackItem->getId() . '.ics';
@@ -226,6 +227,10 @@ public function getBoardId(): int {
226227
return $this->board->getId();
227228
}
228229

230+
public function getStackId(): ?int {
231+
return $this->stack?->getId();
232+
}
233+
229234
public function propPatch(PropPatch $propPatch) {
230235
$properties = [
231236
'{DAV:}displayname',

lib/DAV/CalendarObject.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public function getSize() {
8181
}
8282

8383
public function delete() {
84-
$this->backend->deleteCalendarObject($this->sourceItem, $this->calendar->getBoardId());
84+
$this->backend->deleteCalendarObject($this->sourceItem, $this->calendar->getBoardId(), $this->calendar->getStackId());
8585
}
8686

8787
public function getName() {

lib/DAV/DeckCalendarBackend.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,17 @@ public function getStack(int $stackId): Stack {
102102
* @param string $name resource name like card-123.ics, deck-card-123.ics or stack-12.ics
103103
* @return Card|Stack|null
104104
*/
105-
public function findCalendarObjectByName(string $name, ?int $boardId = null, ?int $stackId = null) {
105+
public function findCalendarObjectByName(string $name, ?int $boardId = null, ?int $stackId = null, bool $includeDeleted = true) {
106106
if (preg_match('/^(?:deck-)?card-(\d+)\.ics$/', $name, $matches) === 1) {
107-
$card = $this->findCardByIdIncludingDeleted((int)$matches[1]);
107+
$cardId = (int)$matches[1];
108+
$card = null;
109+
try {
110+
$card = $includeDeleted
111+
? $this->findCardByIdIncludingDeleted($cardId)
112+
: $this->cardService->find($cardId);
113+
} catch (\Throwable $e) {
114+
$card = null;
115+
}
108116
if ($card === null) {
109117
return null;
110118
}
@@ -296,7 +304,7 @@ public function updateCalendarObject($sourceItem, string $data) {
296304
/**
297305
* @param Card|Stack $sourceItem
298306
*/
299-
public function deleteCalendarObject($sourceItem, ?int $expectedBoardId = null): void {
307+
public function deleteCalendarObject($sourceItem, ?int $expectedBoardId = null, ?int $expectedStackId = null): void {
300308
if ($sourceItem instanceof Card) {
301309
$currentCard = $sourceItem;
302310
if ($expectedBoardId !== null) {
@@ -307,6 +315,10 @@ public function deleteCalendarObject($sourceItem, ?int $expectedBoardId = null):
307315
// Ignore trailing delete from source calendar after a cross-board move.
308316
return;
309317
}
318+
if ($expectedStackId !== null && $currentCard->getStackId() !== $expectedStackId) {
319+
// Ignore trailing delete from source list calendar after an in-board move.
320+
return;
321+
}
310322
} catch (\Throwable $e) {
311323
// If we cannot resolve the current card, continue with normal delete behavior.
312324
}

tests/unit/DAV/DeckCalendarBackendTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,46 @@ public function testDeleteStackFromCalendarObject(): void {
186186
$this->backend->deleteCalendarObject($sourceStack);
187187
}
188188

189+
public function testDeleteCardFromCalendarObjectSkipsTrailingDeleteAfterSameBoardStackMove(): void {
190+
$sourceCard = new Card();
191+
$sourceCard->setId(321);
192+
$sourceCard->setStackId(10);
193+
194+
$currentCard = new Card();
195+
$currentCard->setId(321);
196+
$currentCard->setStackId(11);
197+
198+
$currentStack = new Stack();
199+
$currentStack->setId(11);
200+
$currentStack->setBoardId(12);
201+
202+
$this->cardService->expects($this->once())
203+
->method('find')
204+
->with(321)
205+
->willReturn($currentCard);
206+
$this->stackService->expects($this->once())
207+
->method('find')
208+
->with(11)
209+
->willReturn($currentStack);
210+
$this->cardService->expects($this->never())
211+
->method('delete');
212+
213+
$this->backend->deleteCalendarObject($sourceCard, 12, 10);
214+
}
215+
216+
public function testFindCalendarObjectByNameWithoutDeletedFallbackSkipsSoftDeletedCard(): void {
217+
$this->cardService->expects($this->once())
218+
->method('find')
219+
->with(321)
220+
->willThrowException(new \Exception('Card not found'));
221+
$this->cardService->expects($this->never())
222+
->method('findIncludingDeletedLite');
223+
224+
$object = $this->backend->findCalendarObjectByName('card-321.ics', 12, null, false);
225+
226+
$this->assertNull($object);
227+
}
228+
189229
public function testUpdateCardWithCompletedWithoutStatusMarksDone(): void {
190230
$sourceCard = new Card();
191231
$sourceCard->setId(123);

0 commit comments

Comments
 (0)