Skip to content

Commit ba68701

Browse files
committed
fix(systemtags): case-insensitive search & prevent duplicates
Signed-off-by: skjnldsv <[email protected]>
1 parent 2b6889b commit ba68701

File tree

3 files changed

+94
-37
lines changed

3 files changed

+94
-37
lines changed

cypress/e2e/systemtags/files-bulk-action.cy.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,58 @@ describe('Systemtags: Files bulk action', { testIsolation: false }, () => {
411411
cy.runOccCommand('config:app:set systemtags restrict_creation_to_admin --value 0')
412412
})
413413
})
414+
415+
it('Can search for tags with insensitive case', () => {
416+
let tagId: string
417+
resetTags()
418+
419+
cy.runOccCommand('tag:add TESTTAG public --output json').then(({ stdout }) => {
420+
const tag = JSON.parse(stdout)
421+
tagId = tag.id
422+
})
423+
424+
cy.createRandomUser().then((user1) => {
425+
files.forEach((file) => {
426+
cy.uploadContent(user1, new Blob([]), 'text/plain', '/' + file)
427+
})
428+
429+
cy.login(user1)
430+
cy.visit('/apps/files')
431+
432+
files.forEach((file) => {
433+
getRowForFile(file).should('be.visible')
434+
})
435+
selectAllFiles()
436+
437+
triggerTagManagementDialogAction()
438+
439+
cy.findByRole('textbox', { name: 'Search or create tag' }).should('be.visible')
440+
cy.findByRole('textbox', { name: 'Search tag' }).should('not.exist')
441+
442+
cy.get('[data-cy-systemtags-picker-input]').type('testtag')
443+
444+
cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 1)
445+
cy.get(`[data-cy-systemtags-picker-tag="${tagId}"]`).should('be.visible')
446+
.findByRole('checkbox').should('not.be.checked')
447+
448+
// Assign the tag
449+
cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData')
450+
cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData')
451+
452+
cy.get(`[data-cy-systemtags-picker-tag="${tagId}"]`).should('be.visible')
453+
.findByRole('checkbox').click({ force: true })
454+
cy.get('[data-cy-systemtags-picker-button-submit]').click()
455+
456+
cy.wait('@getTagData')
457+
cy.wait('@assignTagData')
458+
459+
expectInlineTagForFile('file1.txt', ['TESTTAG'])
460+
expectInlineTagForFile('file2.txt', ['TESTTAG'])
461+
expectInlineTagForFile('file3.txt', ['TESTTAG'])
462+
expectInlineTagForFile('file4.txt', ['TESTTAG'])
463+
expectInlineTagForFile('file5.txt', ['TESTTAG'])
464+
465+
cy.get('[data-cy-systemtags-picker]').should('not.exist')
466+
})
467+
})
414468
})

lib/private/SystemTag/SystemTagManager.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public function getAllTags($visibilityFilter = null, $nameSearchPattern = null):
108108

109109
if (!empty($nameSearchPattern)) {
110110
$query->andWhere(
111-
$query->expr()->like(
111+
$query->expr()->iLike(
112112
'name',
113113
$query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern) . '%')
114114
)
@@ -120,7 +120,7 @@ public function getAllTags($visibilityFilter = null, $nameSearchPattern = null):
120120
->addOrderBy('visibility', 'ASC')
121121
->addOrderBy('editable', 'ASC');
122122

123-
$result = $query->execute();
123+
$result = $query->executeQuery();
124124
while ($row = $result->fetch()) {
125125
$tags[$row['id']] = $this->createSystemTagFromRow($row);
126126
}
@@ -156,6 +156,14 @@ public function createTag(string $tagName, bool $userVisible, bool $userAssignab
156156
throw new TagCreationForbiddenException();
157157
}
158158

159+
// Check if tag already exists (case-insensitive)
160+
$existingTags = $this->getAllTags(null, $tagName);
161+
foreach ($existingTags as $existingTag) {
162+
if (mb_strtolower($existingTag->getName()) === mb_strtolower($tagName)) {
163+
throw new TagAlreadyExistsException('Tag ' . $tagName . ' already exists');
164+
}
165+
}
166+
159167
// Length of name column is 64
160168
$truncatedTagName = substr($tagName, 0, 64);
161169
$query = $this->connection->getQueryBuilder();
@@ -226,6 +234,15 @@ public function updateTag(
226234
$color
227235
);
228236

237+
// Check if tag already exists (case-insensitive)
238+
$existingTags = $this->getAllTags(null, $truncatedNewName);
239+
foreach ($existingTags as $existingTag) {
240+
if (mb_strtolower($existingTag->getName()) === mb_strtolower($truncatedNewName)
241+
&& $existingTag->getId() !== $tagId) {
242+
throw new TagAlreadyExistsException('Tag ' . $truncatedNewName . ' already exists');
243+
}
244+
}
245+
229246
$query = $this->connection->getQueryBuilder();
230247
$query->update(self::TAG_TABLE)
231248
->set('name', $query->createParameter('name'))

tests/lib/SystemTag/SystemTagManagerTest.php

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,6 @@ public static function getAllTagsDataProvider() {
7979
['two', false, false],
8080
]
8181
],
82-
[
83-
// duplicate names, different flags
84-
[
85-
['one', false, false],
86-
['one', true, false],
87-
['one', false, true],
88-
['one', true, true],
89-
['two', false, false],
90-
['two', false, true],
91-
]
92-
]
9382
];
9483
}
9584

@@ -162,14 +151,14 @@ public static function getAllTagsFilteredDataProvider() {
162151
[
163152
[
164153
['one', true, false],
165-
['one', false, false],
154+
['one_different', false, false],
166155
['two', true, false],
167156
],
168157
null,
169158
'on',
170159
[
171160
['one', true, false],
172-
['one', false, false],
161+
['one_different', false, false],
173162
]
174163
],
175164
// filter by name pattern and visibility
@@ -178,7 +167,7 @@ public static function getAllTagsFilteredDataProvider() {
178167
[
179168
['one', true, false],
180169
['two', true, false],
181-
['one', false, false],
170+
['one_different', false, false],
182171
],
183172
true,
184173
'on',
@@ -249,6 +238,15 @@ public function testCreateDuplicate($name, $userVisible, $userAssignable): void
249238
$this->tagManager->createTag($name, $userVisible, $userAssignable);
250239
}
251240

241+
public function testCreateDuplicateWithDifferentFlags(): void {
242+
$this->expectException(TagAlreadyExistsException::class);
243+
244+
// Create a tag with specific flags
245+
$this->tagManager->createTag('duplicate', true, false);
246+
// Try to create a tag with the same name but different flags - should fail
247+
$this->tagManager->createTag('duplicate', false, true);
248+
}
249+
252250
public function testCreateOverlongName(): void {
253251
$tag = $this->tagManager->createTag('Zona circundante do Palácio Nacional da Ajuda (Jardim das Damas, Salão de Física, Torre Sineira, Paço Velho e Jardim Botânico)', true, true);
254252
$this->assertSame('Zona circundante do Palácio Nacional da Ajuda (Jardim das Damas', $tag->getName()); // 63 characters but 64 bytes due to "á"
@@ -356,32 +354,20 @@ public function testUpdateTag($tagCreate, $tagUpdated): void {
356354

357355
}
358356

359-
/**
360-
* @dataProvider updateTagProvider
361-
*/
362-
public function testUpdateTagDuplicate($tagCreate, $tagUpdated): void {
357+
public function testUpdateTagToExistingName(): void {
363358
$this->expectException(\OCP\SystemTag\TagAlreadyExistsException::class);
364359

365-
$this->tagManager->createTag(
366-
$tagCreate[0],
367-
$tagCreate[1],
368-
$tagCreate[2],
369-
$tagCreate[3],
370-
);
371-
$tag2 = $this->tagManager->createTag(
372-
$tagUpdated[0],
373-
$tagUpdated[1],
374-
$tagUpdated[2],
375-
$tagUpdated[3],
376-
);
360+
// Create two different tags
361+
$tag1 = $this->tagManager->createTag('first', true, true);
362+
$tag2 = $this->tagManager->createTag('second', false, false);
377363

378-
// update to match the first tag
364+
// Try to update tag2 to have the same name as tag1 - should fail
379365
$this->tagManager->updateTag(
380366
$tag2->getId(),
381-
$tagCreate[0],
382-
$tagCreate[1],
383-
$tagCreate[2],
384-
$tagCreate[3],
367+
'first',
368+
false,
369+
false,
370+
null
385371
);
386372
}
387373

0 commit comments

Comments
 (0)