Completed
Push — master ( 6cc548...53028d )
by John
23:01 queued 13s
created
tests/lib/SystemTag/SystemTagManagerTest.php 1 patch
Indentation   +558 added lines, -558 removed lines patch added patch discarded remove patch
@@ -30,562 +30,562 @@
 block discarded – undo
30 30
  * @package Test\SystemTag
31 31
  */
32 32
 class SystemTagManagerTest extends TestCase {
33
-	private ISystemTagManager $tagManager;
34
-	private IDBConnection $connection;
35
-	private IGroupManager $groupManager;
36
-	private IUserSession $userSession;
37
-	private IAppConfig $appConfig;
38
-	private IEventDispatcher $dispatcher;
39
-
40
-	protected function setUp(): void {
41
-		parent::setUp();
42
-
43
-		$this->connection = Server::get(IDBConnection::class);
44
-
45
-		$this->dispatcher = $this->createMock(IEventDispatcher::class);
46
-		$this->groupManager = $this->createMock(IGroupManager::class);
47
-		$this->userSession = $this->createMock(IUserSession::class);
48
-		$this->appConfig = $this->createMock(IAppConfig::class);
49
-
50
-		$this->tagManager = new SystemTagManager(
51
-			$this->connection,
52
-			$this->groupManager,
53
-			$this->dispatcher,
54
-			$this->userSession,
55
-			$this->appConfig,
56
-		);
57
-		$this->pruneTagsTables();
58
-	}
59
-
60
-	protected function tearDown(): void {
61
-		$this->pruneTagsTables();
62
-		\OC::$CLI = true;
63
-		parent::tearDown();
64
-	}
65
-
66
-	protected function pruneTagsTables() {
67
-		$query = $this->connection->getQueryBuilder();
68
-		$query->delete(SystemTagObjectMapper::RELATION_TABLE)->execute();
69
-		$query->delete(SystemTagManager::TAG_TABLE)->execute();
70
-	}
71
-
72
-	public static function getAllTagsDataProvider(): array {
73
-		return [
74
-			[
75
-				// no tags at all
76
-				[]
77
-			],
78
-			[
79
-				// simple
80
-				[
81
-					['one', false, false],
82
-					['two', false, false],
83
-				]
84
-			],
85
-		];
86
-	}
87
-
88
-	#[\PHPUnit\Framework\Attributes\DataProvider('getAllTagsDataProvider')]
89
-	public function testGetAllTags($testTags): void {
90
-		$testTagsById = [];
91
-		foreach ($testTags as $testTag) {
92
-			$tag = $this->tagManager->createTag($testTag[0], $testTag[1], $testTag[2]);
93
-			$testTagsById[$tag->getId()] = $tag;
94
-		}
95
-
96
-		$tagList = $this->tagManager->getAllTags();
97
-
98
-		$this->assertCount(count($testTags), $tagList);
99
-
100
-		foreach ($testTagsById as $testTagId => $testTag) {
101
-			$this->assertTrue(isset($tagList[$testTagId]));
102
-			$this->assertSameTag($tagList[$testTagId], $testTag);
103
-		}
104
-	}
105
-
106
-	public static function getAllTagsFilteredDataProvider(): array {
107
-		return [
108
-			[
109
-				[
110
-					// no tags at all
111
-				],
112
-				null,
113
-				null,
114
-				[]
115
-			],
116
-			// filter by visible only
117
-			[
118
-				// none visible
119
-				[
120
-					['one', false, false],
121
-					['two', false, false],
122
-				],
123
-				true,
124
-				null,
125
-				[]
126
-			],
127
-			[
128
-				// one visible
129
-				[
130
-					['one', true, false],
131
-					['two', false, false],
132
-				],
133
-				true,
134
-				null,
135
-				[
136
-					['one', true, false],
137
-				]
138
-			],
139
-			[
140
-				// one invisible
141
-				[
142
-					['one', true, false],
143
-					['two', false, false],
144
-				],
145
-				false,
146
-				null,
147
-				[
148
-					['two', false, false],
149
-				]
150
-			],
151
-			// filter by name pattern
152
-			[
153
-				[
154
-					['one', true, false],
155
-					['one_different', false, false],
156
-					['two', true, false],
157
-				],
158
-				null,
159
-				'on',
160
-				[
161
-					['one', true, false],
162
-					['one_different', false, false],
163
-				]
164
-			],
165
-			// filter by name pattern and visibility
166
-			[
167
-				// one visible
168
-				[
169
-					['one', true, false],
170
-					['two', true, false],
171
-					['one_different', false, false],
172
-				],
173
-				true,
174
-				'on',
175
-				[
176
-					['one', true, false],
177
-				]
178
-			],
179
-			// filter by name pattern in the middle
180
-			[
181
-				// one visible
182
-				[
183
-					['abcdefghi', true, false],
184
-					['two', true, false],
185
-				],
186
-				null,
187
-				'def',
188
-				[
189
-					['abcdefghi', true, false],
190
-				]
191
-			]
192
-		];
193
-	}
194
-
195
-	#[\PHPUnit\Framework\Attributes\DataProvider('getAllTagsFilteredDataProvider')]
196
-	public function testGetAllTagsFiltered($testTags, $visibilityFilter, $nameSearch, $expectedResults): void {
197
-		foreach ($testTags as $testTag) {
198
-			$this->tagManager->createTag($testTag[0], $testTag[1], $testTag[2]);
199
-		}
200
-
201
-		$testTagsById = [];
202
-		foreach ($expectedResults as $expectedTag) {
203
-			$tag = $this->tagManager->getTag($expectedTag[0], $expectedTag[1], $expectedTag[2]);
204
-			$testTagsById[$tag->getId()] = $tag;
205
-		}
206
-
207
-		$tagList = $this->tagManager->getAllTags($visibilityFilter, $nameSearch);
208
-
209
-		$this->assertCount(count($testTagsById), $tagList);
210
-
211
-		foreach ($testTagsById as $testTagId => $testTag) {
212
-			$this->assertTrue(isset($tagList[$testTagId]));
213
-			$this->assertSameTag($tagList[$testTagId], $testTag);
214
-		}
215
-	}
216
-
217
-	public static function oneTagMultipleFlagsProvider(): array {
218
-		return [
219
-			['one', false, false],
220
-			['one', true, false],
221
-			['one', false, true],
222
-			['one', true, true],
223
-		];
224
-	}
225
-
226
-	#[\PHPUnit\Framework\Attributes\DataProvider('oneTagMultipleFlagsProvider')]
227
-	public function testCreateDuplicate($name, $userVisible, $userAssignable): void {
228
-		$this->expectException(TagAlreadyExistsException::class);
229
-
230
-		try {
231
-			$this->tagManager->createTag($name, $userVisible, $userAssignable);
232
-		} catch (\Exception $e) {
233
-			$this->assertTrue(false, 'No exception thrown for the first create call');
234
-		}
235
-		$this->tagManager->createTag($name, $userVisible, $userAssignable);
236
-	}
237
-
238
-	public function testCreateDuplicateWithDifferentFlags(): void {
239
-		$this->expectException(TagAlreadyExistsException::class);
240
-
241
-		// Create a tag with specific flags
242
-		$this->tagManager->createTag('duplicate', true, false);
243
-		// Try to create a tag with the same name but different flags - should fail
244
-		$this->tagManager->createTag('duplicate', false, true);
245
-	}
246
-
247
-	public function testCreateOverlongName(): void {
248
-		$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);
249
-		$this->assertSame('Zona circundante do Palácio Nacional da Ajuda (Jardim das Damas', $tag->getName()); // 63 characters but 64 bytes due to "á"
250
-	}
251
-
252
-	#[\PHPUnit\Framework\Attributes\DataProvider('oneTagMultipleFlagsProvider')]
253
-	public function testGetExistingTag($name, $userVisible, $userAssignable): void {
254
-		$tag1 = $this->tagManager->createTag($name, $userVisible, $userAssignable);
255
-		$tag2 = $this->tagManager->getTag($name, $userVisible, $userAssignable);
256
-
257
-		$this->assertSameTag($tag1, $tag2);
258
-	}
259
-
260
-	public function testGetExistingTagById(): void {
261
-		$tag1 = $this->tagManager->createTag('one', true, false);
262
-		$tag2 = $this->tagManager->createTag('two', false, true);
263
-
264
-		$tagList = $this->tagManager->getTagsByIds([$tag1->getId(), $tag2->getId()]);
265
-
266
-		$this->assertCount(2, $tagList);
267
-
268
-		$this->assertSameTag($tag1, $tagList[$tag1->getId()]);
269
-		$this->assertSameTag($tag2, $tagList[$tag2->getId()]);
270
-	}
271
-
272
-
273
-	public function testGetNonExistingTag(): void {
274
-		$this->expectException(TagNotFoundException::class);
275
-
276
-		$this->tagManager->getTag('nonexist', false, false);
277
-	}
278
-
279
-
280
-	public function testGetNonExistingTagsById(): void {
281
-		$this->expectException(TagNotFoundException::class);
282
-
283
-		$tag1 = $this->tagManager->createTag('one', true, false);
284
-		$this->tagManager->getTagsByIds([$tag1->getId(), 100, 101]);
285
-	}
286
-
287
-
288
-	public function testGetInvalidTagIdFormat(): void {
289
-		$this->expectException(\InvalidArgumentException::class);
290
-
291
-		$tag1 = $this->tagManager->createTag('one', true, false);
292
-		$this->tagManager->getTagsByIds([$tag1->getId() . 'suffix']);
293
-	}
294
-
295
-	public static function updateTagProvider(): array {
296
-		return [
297
-			[
298
-				// update name
299
-				['one', true, true, '0082c9'],
300
-				['two', true, true, '0082c9']
301
-			],
302
-			[
303
-				// update one flag
304
-				['one', false, true, null],
305
-				['one', true, true, '0082c9']
306
-			],
307
-			[
308
-				// update all flags
309
-				['one', false, false, '0082c9'],
310
-				['one', true, true, null]
311
-			],
312
-			[
313
-				// update all
314
-				['one', false, false, '0082c9'],
315
-				['two', true, true, '0082c9']
316
-			],
317
-		];
318
-	}
319
-
320
-	#[\PHPUnit\Framework\Attributes\DataProvider('updateTagProvider')]
321
-	public function testUpdateTag($tagCreate, $tagUpdated): void {
322
-		$tag1 = $this->tagManager->createTag(
323
-			$tagCreate[0],
324
-			$tagCreate[1],
325
-			$tagCreate[2],
326
-			$tagCreate[3],
327
-		);
328
-		$this->tagManager->updateTag(
329
-			$tag1->getId(),
330
-			$tagUpdated[0],
331
-			$tagUpdated[1],
332
-			$tagUpdated[2],
333
-			$tagUpdated[3],
334
-		);
335
-		$tag2 = $this->tagManager->getTag(
336
-			$tagUpdated[0],
337
-			$tagUpdated[1],
338
-			$tagUpdated[2],
339
-			$tagUpdated[3],
340
-		);
341
-
342
-		$this->assertEquals($tag2->getId(), $tag1->getId());
343
-		$this->assertEquals($tag2->getName(), $tagUpdated[0]);
344
-		$this->assertEquals($tag2->isUserVisible(), $tagUpdated[1]);
345
-		$this->assertEquals($tag2->isUserAssignable(), $tagUpdated[2]);
346
-		$this->assertEquals($tag2->getColor(), $tagUpdated[3]);
347
-
348
-	}
349
-
350
-	public function testUpdateTagToExistingName(): void {
351
-		$this->expectException(TagAlreadyExistsException::class);
352
-
353
-		// Create two different tags
354
-		$tag1 = $this->tagManager->createTag('first', true, true);
355
-		$tag2 = $this->tagManager->createTag('second', false, false);
356
-
357
-		// Try to update tag2 to have the same name as tag1 - should fail
358
-		$this->tagManager->updateTag(
359
-			$tag2->getId(),
360
-			'first',
361
-			false,
362
-			false,
363
-			null
364
-		);
365
-	}
366
-
367
-	public function testDeleteTags(): void {
368
-		$tag1 = $this->tagManager->createTag('one', true, false);
369
-		$tag2 = $this->tagManager->createTag('two', false, true);
370
-
371
-		$this->tagManager->deleteTags([$tag1->getId(), $tag2->getId()]);
372
-
373
-		$this->assertEmpty($this->tagManager->getAllTags());
374
-	}
375
-
376
-
377
-	public function testDeleteNonExistingTag(): void {
378
-		$this->expectException(TagNotFoundException::class);
379
-
380
-		$this->tagManager->deleteTags([100]);
381
-	}
382
-
383
-	public function testDeleteTagRemovesRelations(): void {
384
-		$tag1 = $this->tagManager->createTag('one', true, false);
385
-		$tag2 = $this->tagManager->createTag('two', true, true);
386
-
387
-		$tagMapper = new SystemTagObjectMapper($this->connection, $this->tagManager, $this->dispatcher);
388
-
389
-		$tagMapper->assignTags(1, 'testtype', $tag1->getId());
390
-		$tagMapper->assignTags(1, 'testtype', $tag2->getId());
391
-		$tagMapper->assignTags(2, 'testtype', $tag1->getId());
392
-
393
-		$this->tagManager->deleteTags($tag1->getId());
394
-
395
-		$tagIdMapping = $tagMapper->getTagIdsForObjects(
396
-			[1, 2],
397
-			'testtype'
398
-		);
399
-
400
-		$this->assertEquals([
401
-			1 => [$tag2->getId()],
402
-			2 => [],
403
-		], $tagIdMapping);
404
-	}
405
-
406
-	public static function visibilityCheckProvider(): array {
407
-		return [
408
-			[false, false, false, false],
409
-			[true, false, false, true],
410
-			[false, false, true, true],
411
-			[true, false, true, true],
412
-		];
413
-	}
414
-
415
-	#[\PHPUnit\Framework\Attributes\DataProvider('visibilityCheckProvider')]
416
-	public function testVisibilityCheck($userVisible, $userAssignable, $isAdmin, $expectedResult): void {
417
-		$user = $this->getMockBuilder(IUser::class)->getMock();
418
-		$user->expects($this->any())
419
-			->method('getUID')
420
-			->willReturn('test');
421
-		$tag1 = $this->tagManager->createTag('one', $userVisible, $userAssignable);
422
-
423
-		$this->groupManager->expects($this->any())
424
-			->method('isAdmin')
425
-			->with('test')
426
-			->willReturn($isAdmin);
427
-
428
-		$this->assertEquals($expectedResult, $this->tagManager->canUserSeeTag($tag1, $user));
429
-	}
430
-
431
-	public static function assignabilityCheckProvider(): array {
432
-		return [
433
-			// no groups
434
-			[false, false, false, false],
435
-			[true, false, false, false],
436
-			[true, true, false, true],
437
-			[false, true, false, false],
438
-			// admin rulez
439
-			[false, false, true, true],
440
-			[false, true, true, true],
441
-			[true, false, true, true],
442
-			[true, true, true, true],
443
-			// ignored groups
444
-			[false, false, false, false, ['group1'], ['group1']],
445
-			[true, true, false, true, ['group1'], ['group1']],
446
-			[true, true, false, true, ['group1'], ['anothergroup']],
447
-			[false, true, false, false, ['group1'], ['group1']],
448
-			// admin has precedence over groups
449
-			[false, false, true, true, ['group1'], ['anothergroup']],
450
-			[false, true, true, true, ['group1'], ['anothergroup']],
451
-			[true, false, true, true, ['group1'], ['anothergroup']],
452
-			[true, true, true, true, ['group1'], ['anothergroup']],
453
-			// groups only checked when visible and user non-assignable and non-admin
454
-			[true, false, false, false, ['group1'], ['anothergroup1']],
455
-			[true, false, false, true, ['group1'], ['group1']],
456
-			[true, false, false, true, ['group1', 'group2'], ['group2', 'group3']],
457
-		];
458
-	}
459
-
460
-	#[\PHPUnit\Framework\Attributes\DataProvider('assignabilityCheckProvider')]
461
-	public function testAssignabilityCheck($userVisible, $userAssignable, $isAdmin, $expectedResult, $userGroupIds = [], $tagGroupIds = []): void {
462
-		$user = $this->getMockBuilder(IUser::class)->getMock();
463
-		$user->expects($this->any())
464
-			->method('getUID')
465
-			->willReturn('test');
466
-		$tag1 = $this->tagManager->createTag('one', $userVisible, $userAssignable);
467
-		$this->tagManager->setTagGroups($tag1, $tagGroupIds);
468
-
469
-		$this->groupManager->expects($this->any())
470
-			->method('isAdmin')
471
-			->with('test')
472
-			->willReturn($isAdmin);
473
-		$this->groupManager->expects($this->any())
474
-			->method('getUserGroupIds')
475
-			->with($user)
476
-			->willReturn($userGroupIds);
477
-
478
-		$this->assertEquals($expectedResult, $this->tagManager->canUserAssignTag($tag1, $user));
479
-	}
480
-
481
-	public function testTagGroups(): void {
482
-		$tag1 = $this->tagManager->createTag('tag1', true, false);
483
-		$tag2 = $this->tagManager->createTag('tag2', true, false);
484
-		$this->tagManager->setTagGroups($tag1, ['group1', 'group2']);
485
-		$this->tagManager->setTagGroups($tag2, ['group2', 'group3']);
486
-
487
-		$this->assertEquals(['group1', 'group2'], $this->tagManager->getTagGroups($tag1));
488
-		$this->assertEquals(['group2', 'group3'], $this->tagManager->getTagGroups($tag2));
489
-
490
-		// change groups
491
-		$this->tagManager->setTagGroups($tag1, ['group3', 'group4']);
492
-		$this->tagManager->setTagGroups($tag2, []);
493
-
494
-		$this->assertEquals(['group3', 'group4'], $this->tagManager->getTagGroups($tag1));
495
-		$this->assertEquals([], $this->tagManager->getTagGroups($tag2));
496
-	}
497
-
498
-	/**
499
-	 * empty groupIds should be ignored
500
-	 */
501
-	public function testEmptyTagGroup(): void {
502
-		$tag1 = $this->tagManager->createTag('tag1', true, false);
503
-		$this->tagManager->setTagGroups($tag1, ['']);
504
-		$this->assertEquals([], $this->tagManager->getTagGroups($tag1));
505
-	}
506
-
507
-	public static function allowedToCreateProvider(): array {
508
-		return [
509
-			[true, null, true],
510
-			[true, null, false],
511
-			[false, true, true],
512
-			[false, true, false],
513
-			[false, false, false],
514
-		];
515
-	}
516
-
517
-	#[\PHPUnit\Framework\Attributes\DataProvider('allowedToCreateProvider')]
518
-	public function testAllowedToCreateTag(bool $isCli, ?bool $isAdmin, bool $isRestricted): void {
519
-		$oldCli = \OC::$CLI;
520
-		\OC::$CLI = $isCli;
521
-
522
-		$user = $this->getMockBuilder(IUser::class)->getMock();
523
-		$user->expects($this->any())
524
-			->method('getUID')
525
-			->willReturn('test');
526
-		$this->userSession->expects($this->any())
527
-			->method('getUser')
528
-			->willReturn($isAdmin === null ? null : $user);
529
-		$this->groupManager->expects($this->any())
530
-			->method('isAdmin')
531
-			->with('test')
532
-			->willReturn($isAdmin);
533
-		$this->appConfig->expects($this->any())
534
-			->method('getValueBool')
535
-			->with('systemtags', 'restrict_creation_to_admin')
536
-			->willReturn($isRestricted);
537
-
538
-		$name = uniqid('tag_', true);
539
-		$tag = $this->tagManager->createTag($name, true, true);
540
-		$this->assertEquals($tag->getName(), $name);
541
-		$this->tagManager->deleteTags($tag->getId());
542
-
543
-		\OC::$CLI = $oldCli;
544
-	}
545
-
546
-	public static function disallowedToCreateProvider(): array {
547
-		return [
548
-			[false],
549
-			[null],
550
-		];
551
-	}
552
-
553
-	#[\PHPUnit\Framework\Attributes\DataProvider('disallowedToCreateProvider')]
554
-	public function testDisallowedToCreateTag(?bool $isAdmin): void {
555
-		$oldCli = \OC::$CLI;
556
-		\OC::$CLI = false;
557
-
558
-		$user = $this->getMockBuilder(IUser::class)->getMock();
559
-		$user->expects($this->any())
560
-			->method('getUID')
561
-			->willReturn('test');
562
-		$this->userSession->expects($this->any())
563
-			->method('getUser')
564
-			->willReturn($isAdmin === null ? null : $user);
565
-		$this->groupManager->expects($this->any())
566
-			->method('isAdmin')
567
-			->with('test')
568
-			->willReturn($isAdmin);
569
-		$this->appConfig->expects($this->any())
570
-			->method('getValueBool')
571
-			->with('systemtags', 'restrict_creation_to_admin')
572
-			->willReturn(true);
573
-
574
-		$this->expectException(\Exception::class);
575
-		$tag = $this->tagManager->createTag(uniqid('tag_', true), true, true);
576
-
577
-		\OC::$CLI = $oldCli;
578
-	}
579
-
580
-
581
-	/**
582
-	 * @param ISystemTag $tag1
583
-	 * @param ISystemTag $tag2
584
-	 */
585
-	private function assertSameTag($tag1, $tag2) {
586
-		$this->assertEquals($tag1->getId(), $tag2->getId());
587
-		$this->assertEquals($tag1->getName(), $tag2->getName());
588
-		$this->assertEquals($tag1->isUserVisible(), $tag2->isUserVisible());
589
-		$this->assertEquals($tag1->isUserAssignable(), $tag2->isUserAssignable());
590
-	}
33
+    private ISystemTagManager $tagManager;
34
+    private IDBConnection $connection;
35
+    private IGroupManager $groupManager;
36
+    private IUserSession $userSession;
37
+    private IAppConfig $appConfig;
38
+    private IEventDispatcher $dispatcher;
39
+
40
+    protected function setUp(): void {
41
+        parent::setUp();
42
+
43
+        $this->connection = Server::get(IDBConnection::class);
44
+
45
+        $this->dispatcher = $this->createMock(IEventDispatcher::class);
46
+        $this->groupManager = $this->createMock(IGroupManager::class);
47
+        $this->userSession = $this->createMock(IUserSession::class);
48
+        $this->appConfig = $this->createMock(IAppConfig::class);
49
+
50
+        $this->tagManager = new SystemTagManager(
51
+            $this->connection,
52
+            $this->groupManager,
53
+            $this->dispatcher,
54
+            $this->userSession,
55
+            $this->appConfig,
56
+        );
57
+        $this->pruneTagsTables();
58
+    }
59
+
60
+    protected function tearDown(): void {
61
+        $this->pruneTagsTables();
62
+        \OC::$CLI = true;
63
+        parent::tearDown();
64
+    }
65
+
66
+    protected function pruneTagsTables() {
67
+        $query = $this->connection->getQueryBuilder();
68
+        $query->delete(SystemTagObjectMapper::RELATION_TABLE)->execute();
69
+        $query->delete(SystemTagManager::TAG_TABLE)->execute();
70
+    }
71
+
72
+    public static function getAllTagsDataProvider(): array {
73
+        return [
74
+            [
75
+                // no tags at all
76
+                []
77
+            ],
78
+            [
79
+                // simple
80
+                [
81
+                    ['one', false, false],
82
+                    ['two', false, false],
83
+                ]
84
+            ],
85
+        ];
86
+    }
87
+
88
+    #[\PHPUnit\Framework\Attributes\DataProvider('getAllTagsDataProvider')]
89
+    public function testGetAllTags($testTags): void {
90
+        $testTagsById = [];
91
+        foreach ($testTags as $testTag) {
92
+            $tag = $this->tagManager->createTag($testTag[0], $testTag[1], $testTag[2]);
93
+            $testTagsById[$tag->getId()] = $tag;
94
+        }
95
+
96
+        $tagList = $this->tagManager->getAllTags();
97
+
98
+        $this->assertCount(count($testTags), $tagList);
99
+
100
+        foreach ($testTagsById as $testTagId => $testTag) {
101
+            $this->assertTrue(isset($tagList[$testTagId]));
102
+            $this->assertSameTag($tagList[$testTagId], $testTag);
103
+        }
104
+    }
105
+
106
+    public static function getAllTagsFilteredDataProvider(): array {
107
+        return [
108
+            [
109
+                [
110
+                    // no tags at all
111
+                ],
112
+                null,
113
+                null,
114
+                []
115
+            ],
116
+            // filter by visible only
117
+            [
118
+                // none visible
119
+                [
120
+                    ['one', false, false],
121
+                    ['two', false, false],
122
+                ],
123
+                true,
124
+                null,
125
+                []
126
+            ],
127
+            [
128
+                // one visible
129
+                [
130
+                    ['one', true, false],
131
+                    ['two', false, false],
132
+                ],
133
+                true,
134
+                null,
135
+                [
136
+                    ['one', true, false],
137
+                ]
138
+            ],
139
+            [
140
+                // one invisible
141
+                [
142
+                    ['one', true, false],
143
+                    ['two', false, false],
144
+                ],
145
+                false,
146
+                null,
147
+                [
148
+                    ['two', false, false],
149
+                ]
150
+            ],
151
+            // filter by name pattern
152
+            [
153
+                [
154
+                    ['one', true, false],
155
+                    ['one_different', false, false],
156
+                    ['two', true, false],
157
+                ],
158
+                null,
159
+                'on',
160
+                [
161
+                    ['one', true, false],
162
+                    ['one_different', false, false],
163
+                ]
164
+            ],
165
+            // filter by name pattern and visibility
166
+            [
167
+                // one visible
168
+                [
169
+                    ['one', true, false],
170
+                    ['two', true, false],
171
+                    ['one_different', false, false],
172
+                ],
173
+                true,
174
+                'on',
175
+                [
176
+                    ['one', true, false],
177
+                ]
178
+            ],
179
+            // filter by name pattern in the middle
180
+            [
181
+                // one visible
182
+                [
183
+                    ['abcdefghi', true, false],
184
+                    ['two', true, false],
185
+                ],
186
+                null,
187
+                'def',
188
+                [
189
+                    ['abcdefghi', true, false],
190
+                ]
191
+            ]
192
+        ];
193
+    }
194
+
195
+    #[\PHPUnit\Framework\Attributes\DataProvider('getAllTagsFilteredDataProvider')]
196
+    public function testGetAllTagsFiltered($testTags, $visibilityFilter, $nameSearch, $expectedResults): void {
197
+        foreach ($testTags as $testTag) {
198
+            $this->tagManager->createTag($testTag[0], $testTag[1], $testTag[2]);
199
+        }
200
+
201
+        $testTagsById = [];
202
+        foreach ($expectedResults as $expectedTag) {
203
+            $tag = $this->tagManager->getTag($expectedTag[0], $expectedTag[1], $expectedTag[2]);
204
+            $testTagsById[$tag->getId()] = $tag;
205
+        }
206
+
207
+        $tagList = $this->tagManager->getAllTags($visibilityFilter, $nameSearch);
208
+
209
+        $this->assertCount(count($testTagsById), $tagList);
210
+
211
+        foreach ($testTagsById as $testTagId => $testTag) {
212
+            $this->assertTrue(isset($tagList[$testTagId]));
213
+            $this->assertSameTag($tagList[$testTagId], $testTag);
214
+        }
215
+    }
216
+
217
+    public static function oneTagMultipleFlagsProvider(): array {
218
+        return [
219
+            ['one', false, false],
220
+            ['one', true, false],
221
+            ['one', false, true],
222
+            ['one', true, true],
223
+        ];
224
+    }
225
+
226
+    #[\PHPUnit\Framework\Attributes\DataProvider('oneTagMultipleFlagsProvider')]
227
+    public function testCreateDuplicate($name, $userVisible, $userAssignable): void {
228
+        $this->expectException(TagAlreadyExistsException::class);
229
+
230
+        try {
231
+            $this->tagManager->createTag($name, $userVisible, $userAssignable);
232
+        } catch (\Exception $e) {
233
+            $this->assertTrue(false, 'No exception thrown for the first create call');
234
+        }
235
+        $this->tagManager->createTag($name, $userVisible, $userAssignable);
236
+    }
237
+
238
+    public function testCreateDuplicateWithDifferentFlags(): void {
239
+        $this->expectException(TagAlreadyExistsException::class);
240
+
241
+        // Create a tag with specific flags
242
+        $this->tagManager->createTag('duplicate', true, false);
243
+        // Try to create a tag with the same name but different flags - should fail
244
+        $this->tagManager->createTag('duplicate', false, true);
245
+    }
246
+
247
+    public function testCreateOverlongName(): void {
248
+        $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);
249
+        $this->assertSame('Zona circundante do Palácio Nacional da Ajuda (Jardim das Damas', $tag->getName()); // 63 characters but 64 bytes due to "á"
250
+    }
251
+
252
+    #[\PHPUnit\Framework\Attributes\DataProvider('oneTagMultipleFlagsProvider')]
253
+    public function testGetExistingTag($name, $userVisible, $userAssignable): void {
254
+        $tag1 = $this->tagManager->createTag($name, $userVisible, $userAssignable);
255
+        $tag2 = $this->tagManager->getTag($name, $userVisible, $userAssignable);
256
+
257
+        $this->assertSameTag($tag1, $tag2);
258
+    }
259
+
260
+    public function testGetExistingTagById(): void {
261
+        $tag1 = $this->tagManager->createTag('one', true, false);
262
+        $tag2 = $this->tagManager->createTag('two', false, true);
263
+
264
+        $tagList = $this->tagManager->getTagsByIds([$tag1->getId(), $tag2->getId()]);
265
+
266
+        $this->assertCount(2, $tagList);
267
+
268
+        $this->assertSameTag($tag1, $tagList[$tag1->getId()]);
269
+        $this->assertSameTag($tag2, $tagList[$tag2->getId()]);
270
+    }
271
+
272
+
273
+    public function testGetNonExistingTag(): void {
274
+        $this->expectException(TagNotFoundException::class);
275
+
276
+        $this->tagManager->getTag('nonexist', false, false);
277
+    }
278
+
279
+
280
+    public function testGetNonExistingTagsById(): void {
281
+        $this->expectException(TagNotFoundException::class);
282
+
283
+        $tag1 = $this->tagManager->createTag('one', true, false);
284
+        $this->tagManager->getTagsByIds([$tag1->getId(), 100, 101]);
285
+    }
286
+
287
+
288
+    public function testGetInvalidTagIdFormat(): void {
289
+        $this->expectException(\InvalidArgumentException::class);
290
+
291
+        $tag1 = $this->tagManager->createTag('one', true, false);
292
+        $this->tagManager->getTagsByIds([$tag1->getId() . 'suffix']);
293
+    }
294
+
295
+    public static function updateTagProvider(): array {
296
+        return [
297
+            [
298
+                // update name
299
+                ['one', true, true, '0082c9'],
300
+                ['two', true, true, '0082c9']
301
+            ],
302
+            [
303
+                // update one flag
304
+                ['one', false, true, null],
305
+                ['one', true, true, '0082c9']
306
+            ],
307
+            [
308
+                // update all flags
309
+                ['one', false, false, '0082c9'],
310
+                ['one', true, true, null]
311
+            ],
312
+            [
313
+                // update all
314
+                ['one', false, false, '0082c9'],
315
+                ['two', true, true, '0082c9']
316
+            ],
317
+        ];
318
+    }
319
+
320
+    #[\PHPUnit\Framework\Attributes\DataProvider('updateTagProvider')]
321
+    public function testUpdateTag($tagCreate, $tagUpdated): void {
322
+        $tag1 = $this->tagManager->createTag(
323
+            $tagCreate[0],
324
+            $tagCreate[1],
325
+            $tagCreate[2],
326
+            $tagCreate[3],
327
+        );
328
+        $this->tagManager->updateTag(
329
+            $tag1->getId(),
330
+            $tagUpdated[0],
331
+            $tagUpdated[1],
332
+            $tagUpdated[2],
333
+            $tagUpdated[3],
334
+        );
335
+        $tag2 = $this->tagManager->getTag(
336
+            $tagUpdated[0],
337
+            $tagUpdated[1],
338
+            $tagUpdated[2],
339
+            $tagUpdated[3],
340
+        );
341
+
342
+        $this->assertEquals($tag2->getId(), $tag1->getId());
343
+        $this->assertEquals($tag2->getName(), $tagUpdated[0]);
344
+        $this->assertEquals($tag2->isUserVisible(), $tagUpdated[1]);
345
+        $this->assertEquals($tag2->isUserAssignable(), $tagUpdated[2]);
346
+        $this->assertEquals($tag2->getColor(), $tagUpdated[3]);
347
+
348
+    }
349
+
350
+    public function testUpdateTagToExistingName(): void {
351
+        $this->expectException(TagAlreadyExistsException::class);
352
+
353
+        // Create two different tags
354
+        $tag1 = $this->tagManager->createTag('first', true, true);
355
+        $tag2 = $this->tagManager->createTag('second', false, false);
356
+
357
+        // Try to update tag2 to have the same name as tag1 - should fail
358
+        $this->tagManager->updateTag(
359
+            $tag2->getId(),
360
+            'first',
361
+            false,
362
+            false,
363
+            null
364
+        );
365
+    }
366
+
367
+    public function testDeleteTags(): void {
368
+        $tag1 = $this->tagManager->createTag('one', true, false);
369
+        $tag2 = $this->tagManager->createTag('two', false, true);
370
+
371
+        $this->tagManager->deleteTags([$tag1->getId(), $tag2->getId()]);
372
+
373
+        $this->assertEmpty($this->tagManager->getAllTags());
374
+    }
375
+
376
+
377
+    public function testDeleteNonExistingTag(): void {
378
+        $this->expectException(TagNotFoundException::class);
379
+
380
+        $this->tagManager->deleteTags([100]);
381
+    }
382
+
383
+    public function testDeleteTagRemovesRelations(): void {
384
+        $tag1 = $this->tagManager->createTag('one', true, false);
385
+        $tag2 = $this->tagManager->createTag('two', true, true);
386
+
387
+        $tagMapper = new SystemTagObjectMapper($this->connection, $this->tagManager, $this->dispatcher);
388
+
389
+        $tagMapper->assignTags(1, 'testtype', $tag1->getId());
390
+        $tagMapper->assignTags(1, 'testtype', $tag2->getId());
391
+        $tagMapper->assignTags(2, 'testtype', $tag1->getId());
392
+
393
+        $this->tagManager->deleteTags($tag1->getId());
394
+
395
+        $tagIdMapping = $tagMapper->getTagIdsForObjects(
396
+            [1, 2],
397
+            'testtype'
398
+        );
399
+
400
+        $this->assertEquals([
401
+            1 => [$tag2->getId()],
402
+            2 => [],
403
+        ], $tagIdMapping);
404
+    }
405
+
406
+    public static function visibilityCheckProvider(): array {
407
+        return [
408
+            [false, false, false, false],
409
+            [true, false, false, true],
410
+            [false, false, true, true],
411
+            [true, false, true, true],
412
+        ];
413
+    }
414
+
415
+    #[\PHPUnit\Framework\Attributes\DataProvider('visibilityCheckProvider')]
416
+    public function testVisibilityCheck($userVisible, $userAssignable, $isAdmin, $expectedResult): void {
417
+        $user = $this->getMockBuilder(IUser::class)->getMock();
418
+        $user->expects($this->any())
419
+            ->method('getUID')
420
+            ->willReturn('test');
421
+        $tag1 = $this->tagManager->createTag('one', $userVisible, $userAssignable);
422
+
423
+        $this->groupManager->expects($this->any())
424
+            ->method('isAdmin')
425
+            ->with('test')
426
+            ->willReturn($isAdmin);
427
+
428
+        $this->assertEquals($expectedResult, $this->tagManager->canUserSeeTag($tag1, $user));
429
+    }
430
+
431
+    public static function assignabilityCheckProvider(): array {
432
+        return [
433
+            // no groups
434
+            [false, false, false, false],
435
+            [true, false, false, false],
436
+            [true, true, false, true],
437
+            [false, true, false, false],
438
+            // admin rulez
439
+            [false, false, true, true],
440
+            [false, true, true, true],
441
+            [true, false, true, true],
442
+            [true, true, true, true],
443
+            // ignored groups
444
+            [false, false, false, false, ['group1'], ['group1']],
445
+            [true, true, false, true, ['group1'], ['group1']],
446
+            [true, true, false, true, ['group1'], ['anothergroup']],
447
+            [false, true, false, false, ['group1'], ['group1']],
448
+            // admin has precedence over groups
449
+            [false, false, true, true, ['group1'], ['anothergroup']],
450
+            [false, true, true, true, ['group1'], ['anothergroup']],
451
+            [true, false, true, true, ['group1'], ['anothergroup']],
452
+            [true, true, true, true, ['group1'], ['anothergroup']],
453
+            // groups only checked when visible and user non-assignable and non-admin
454
+            [true, false, false, false, ['group1'], ['anothergroup1']],
455
+            [true, false, false, true, ['group1'], ['group1']],
456
+            [true, false, false, true, ['group1', 'group2'], ['group2', 'group3']],
457
+        ];
458
+    }
459
+
460
+    #[\PHPUnit\Framework\Attributes\DataProvider('assignabilityCheckProvider')]
461
+    public function testAssignabilityCheck($userVisible, $userAssignable, $isAdmin, $expectedResult, $userGroupIds = [], $tagGroupIds = []): void {
462
+        $user = $this->getMockBuilder(IUser::class)->getMock();
463
+        $user->expects($this->any())
464
+            ->method('getUID')
465
+            ->willReturn('test');
466
+        $tag1 = $this->tagManager->createTag('one', $userVisible, $userAssignable);
467
+        $this->tagManager->setTagGroups($tag1, $tagGroupIds);
468
+
469
+        $this->groupManager->expects($this->any())
470
+            ->method('isAdmin')
471
+            ->with('test')
472
+            ->willReturn($isAdmin);
473
+        $this->groupManager->expects($this->any())
474
+            ->method('getUserGroupIds')
475
+            ->with($user)
476
+            ->willReturn($userGroupIds);
477
+
478
+        $this->assertEquals($expectedResult, $this->tagManager->canUserAssignTag($tag1, $user));
479
+    }
480
+
481
+    public function testTagGroups(): void {
482
+        $tag1 = $this->tagManager->createTag('tag1', true, false);
483
+        $tag2 = $this->tagManager->createTag('tag2', true, false);
484
+        $this->tagManager->setTagGroups($tag1, ['group1', 'group2']);
485
+        $this->tagManager->setTagGroups($tag2, ['group2', 'group3']);
486
+
487
+        $this->assertEquals(['group1', 'group2'], $this->tagManager->getTagGroups($tag1));
488
+        $this->assertEquals(['group2', 'group3'], $this->tagManager->getTagGroups($tag2));
489
+
490
+        // change groups
491
+        $this->tagManager->setTagGroups($tag1, ['group3', 'group4']);
492
+        $this->tagManager->setTagGroups($tag2, []);
493
+
494
+        $this->assertEquals(['group3', 'group4'], $this->tagManager->getTagGroups($tag1));
495
+        $this->assertEquals([], $this->tagManager->getTagGroups($tag2));
496
+    }
497
+
498
+    /**
499
+     * empty groupIds should be ignored
500
+     */
501
+    public function testEmptyTagGroup(): void {
502
+        $tag1 = $this->tagManager->createTag('tag1', true, false);
503
+        $this->tagManager->setTagGroups($tag1, ['']);
504
+        $this->assertEquals([], $this->tagManager->getTagGroups($tag1));
505
+    }
506
+
507
+    public static function allowedToCreateProvider(): array {
508
+        return [
509
+            [true, null, true],
510
+            [true, null, false],
511
+            [false, true, true],
512
+            [false, true, false],
513
+            [false, false, false],
514
+        ];
515
+    }
516
+
517
+    #[\PHPUnit\Framework\Attributes\DataProvider('allowedToCreateProvider')]
518
+    public function testAllowedToCreateTag(bool $isCli, ?bool $isAdmin, bool $isRestricted): void {
519
+        $oldCli = \OC::$CLI;
520
+        \OC::$CLI = $isCli;
521
+
522
+        $user = $this->getMockBuilder(IUser::class)->getMock();
523
+        $user->expects($this->any())
524
+            ->method('getUID')
525
+            ->willReturn('test');
526
+        $this->userSession->expects($this->any())
527
+            ->method('getUser')
528
+            ->willReturn($isAdmin === null ? null : $user);
529
+        $this->groupManager->expects($this->any())
530
+            ->method('isAdmin')
531
+            ->with('test')
532
+            ->willReturn($isAdmin);
533
+        $this->appConfig->expects($this->any())
534
+            ->method('getValueBool')
535
+            ->with('systemtags', 'restrict_creation_to_admin')
536
+            ->willReturn($isRestricted);
537
+
538
+        $name = uniqid('tag_', true);
539
+        $tag = $this->tagManager->createTag($name, true, true);
540
+        $this->assertEquals($tag->getName(), $name);
541
+        $this->tagManager->deleteTags($tag->getId());
542
+
543
+        \OC::$CLI = $oldCli;
544
+    }
545
+
546
+    public static function disallowedToCreateProvider(): array {
547
+        return [
548
+            [false],
549
+            [null],
550
+        ];
551
+    }
552
+
553
+    #[\PHPUnit\Framework\Attributes\DataProvider('disallowedToCreateProvider')]
554
+    public function testDisallowedToCreateTag(?bool $isAdmin): void {
555
+        $oldCli = \OC::$CLI;
556
+        \OC::$CLI = false;
557
+
558
+        $user = $this->getMockBuilder(IUser::class)->getMock();
559
+        $user->expects($this->any())
560
+            ->method('getUID')
561
+            ->willReturn('test');
562
+        $this->userSession->expects($this->any())
563
+            ->method('getUser')
564
+            ->willReturn($isAdmin === null ? null : $user);
565
+        $this->groupManager->expects($this->any())
566
+            ->method('isAdmin')
567
+            ->with('test')
568
+            ->willReturn($isAdmin);
569
+        $this->appConfig->expects($this->any())
570
+            ->method('getValueBool')
571
+            ->with('systemtags', 'restrict_creation_to_admin')
572
+            ->willReturn(true);
573
+
574
+        $this->expectException(\Exception::class);
575
+        $tag = $this->tagManager->createTag(uniqid('tag_', true), true, true);
576
+
577
+        \OC::$CLI = $oldCli;
578
+    }
579
+
580
+
581
+    /**
582
+     * @param ISystemTag $tag1
583
+     * @param ISystemTag $tag2
584
+     */
585
+    private function assertSameTag($tag1, $tag2) {
586
+        $this->assertEquals($tag1->getId(), $tag2->getId());
587
+        $this->assertEquals($tag1->getName(), $tag2->getName());
588
+        $this->assertEquals($tag1->isUserVisible(), $tag2->isUserVisible());
589
+        $this->assertEquals($tag1->isUserAssignable(), $tag2->isUserAssignable());
590
+    }
591 591
 }
Please login to merge, or discard this patch.
lib/private/SystemTag/SystemTagManager.php 2 patches
Indentation   +417 added lines, -417 removed lines patch added patch discarded remove patch
@@ -28,422 +28,422 @@
 block discarded – undo
28 28
  * Manager class for system tags
29 29
  */
30 30
 class SystemTagManager implements ISystemTagManager {
31
-	public const TAG_TABLE = 'systemtag';
32
-	public const TAG_GROUP_TABLE = 'systemtag_group';
33
-
34
-	/**
35
-	 * Prepared query for selecting tags directly
36
-	 */
37
-	private IQueryBuilder $selectTagQuery;
38
-
39
-	public function __construct(
40
-		protected IDBConnection $connection,
41
-		protected IGroupManager $groupManager,
42
-		protected IEventDispatcher $dispatcher,
43
-		private IUserSession $userSession,
44
-		private IAppConfig $appConfig,
45
-	) {
46
-		$query = $this->connection->getQueryBuilder();
47
-		$this->selectTagQuery = $query->select('*')
48
-			->from(self::TAG_TABLE)
49
-			->where($query->expr()->eq('name', $query->createParameter('name')))
50
-			->andWhere($query->expr()->eq('visibility', $query->createParameter('visibility')))
51
-			->andWhere($query->expr()->eq('editable', $query->createParameter('editable')));
52
-	}
53
-
54
-	public function getTagsByIds($tagIds, ?IUser $user = null): array {
55
-		if (!\is_array($tagIds)) {
56
-			$tagIds = [$tagIds];
57
-		}
58
-
59
-		$tags = [];
60
-
61
-		// note: not all databases will fail if it's a string or starts with a number
62
-		foreach ($tagIds as $tagId) {
63
-			if (!is_numeric($tagId)) {
64
-				throw new \InvalidArgumentException('Tag id must be integer');
65
-			}
66
-		}
67
-
68
-		$query = $this->connection->getQueryBuilder();
69
-		$query->select('*')
70
-			->from(self::TAG_TABLE)
71
-			->where($query->expr()->in('id', $query->createParameter('tagids')))
72
-			->addOrderBy('name', 'ASC')
73
-			->addOrderBy('visibility', 'ASC')
74
-			->addOrderBy('editable', 'ASC')
75
-			->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY);
76
-
77
-		$result = $query->execute();
78
-		while ($row = $result->fetch()) {
79
-			$tag = $this->createSystemTagFromRow($row);
80
-			if ($user && !$this->canUserSeeTag($tag, $user)) {
81
-				// if a user is given, hide invisible tags
82
-				continue;
83
-			}
84
-			$tags[$row['id']] = $tag;
85
-		}
86
-
87
-		$result->closeCursor();
88
-
89
-		if (\count($tags) !== \count($tagIds)) {
90
-			throw new TagNotFoundException(
91
-				'Tag id(s) not found', 0, null, array_diff($tagIds, array_keys($tags))
92
-			);
93
-		}
94
-
95
-		return $tags;
96
-	}
97
-
98
-	public function getAllTags($visibilityFilter = null, $nameSearchPattern = null): array {
99
-		$tags = [];
100
-
101
-		$query = $this->connection->getQueryBuilder();
102
-		$query->select('*')
103
-			->from(self::TAG_TABLE);
104
-
105
-		if (!\is_null($visibilityFilter)) {
106
-			$query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int)$visibilityFilter)));
107
-		}
108
-
109
-		if (!empty($nameSearchPattern)) {
110
-			$query->andWhere(
111
-				$query->expr()->iLike(
112
-					'name',
113
-					$query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern) . '%')
114
-				)
115
-			);
116
-		}
117
-
118
-		$query
119
-			->addOrderBy('name', 'ASC')
120
-			->addOrderBy('visibility', 'ASC')
121
-			->addOrderBy('editable', 'ASC');
122
-
123
-		$result = $query->executeQuery();
124
-		while ($row = $result->fetch()) {
125
-			$tags[$row['id']] = $this->createSystemTagFromRow($row);
126
-		}
127
-
128
-		$result->closeCursor();
129
-
130
-		return $tags;
131
-	}
132
-
133
-	public function getTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
134
-		// Length of name column is 64
135
-		$truncatedTagName = substr($tagName, 0, 64);
136
-		$result = $this->selectTagQuery
137
-			->setParameter('name', $truncatedTagName)
138
-			->setParameter('visibility', $userVisible ? 1 : 0)
139
-			->setParameter('editable', $userAssignable ? 1 : 0)
140
-			->execute();
141
-
142
-		$row = $result->fetch();
143
-		$result->closeCursor();
144
-		if (!$row) {
145
-			throw new TagNotFoundException(
146
-				'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') does not exist'
147
-			);
148
-		}
149
-
150
-		return $this->createSystemTagFromRow($row);
151
-	}
152
-
153
-	public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
154
-		$user = $this->userSession->getUser();
155
-		if (!$this->canUserCreateTag($user)) {
156
-			throw new TagCreationForbiddenException();
157
-		}
158
-
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
-
167
-		// Length of name column is 64
168
-		$truncatedTagName = substr($tagName, 0, 64);
169
-		$query = $this->connection->getQueryBuilder();
170
-		$query->insert(self::TAG_TABLE)
171
-			->values([
172
-				'name' => $query->createNamedParameter($truncatedTagName),
173
-				'visibility' => $query->createNamedParameter($userVisible ? 1 : 0),
174
-				'editable' => $query->createNamedParameter($userAssignable ? 1 : 0),
175
-				'etag' => $query->createNamedParameter(md5((string)time())),
176
-			]);
177
-
178
-		try {
179
-			$query->execute();
180
-		} catch (UniqueConstraintViolationException $e) {
181
-			throw new TagAlreadyExistsException(
182
-				'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
183
-				0,
184
-				$e
185
-			);
186
-		}
187
-
188
-		$tagId = $query->getLastInsertId();
189
-
190
-		$tag = new SystemTag(
191
-			(string)$tagId,
192
-			$truncatedTagName,
193
-			$userVisible,
194
-			$userAssignable
195
-		);
196
-
197
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_CREATE, new ManagerEvent(
198
-			ManagerEvent::EVENT_CREATE, $tag
199
-		));
200
-
201
-		return $tag;
202
-	}
203
-
204
-	public function updateTag(
205
-		string $tagId,
206
-		string $newName,
207
-		bool $userVisible,
208
-		bool $userAssignable,
209
-		?string $color,
210
-	): void {
211
-		try {
212
-			$tags = $this->getTagsByIds($tagId);
213
-		} catch (TagNotFoundException $e) {
214
-			throw new TagNotFoundException(
215
-				'Tag does not exist', 0, null, [$tagId]
216
-			);
217
-		}
218
-
219
-		$user = $this->userSession->getUser();
220
-		if (!$this->canUserUpdateTag($user)) {
221
-			throw new TagUpdateForbiddenException();
222
-		}
223
-
224
-		$beforeUpdate = array_shift($tags);
225
-		// Length of name column is 64
226
-		$newName = trim($newName);
227
-		$truncatedNewName = substr($newName, 0, 64);
228
-		$afterUpdate = new SystemTag(
229
-			$tagId,
230
-			$truncatedNewName,
231
-			$userVisible,
232
-			$userAssignable,
233
-			$beforeUpdate->getETag(),
234
-			$color
235
-		);
236
-
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
-
246
-		$query = $this->connection->getQueryBuilder();
247
-		$query->update(self::TAG_TABLE)
248
-			->set('name', $query->createParameter('name'))
249
-			->set('visibility', $query->createParameter('visibility'))
250
-			->set('editable', $query->createParameter('editable'))
251
-			->set('color', $query->createParameter('color'))
252
-			->where($query->expr()->eq('id', $query->createParameter('tagid')))
253
-			->setParameter('name', $truncatedNewName)
254
-			->setParameter('visibility', $userVisible ? 1 : 0)
255
-			->setParameter('editable', $userAssignable ? 1 : 0)
256
-			->setParameter('tagid', $tagId)
257
-			->setParameter('color', $color);
258
-
259
-		try {
260
-			if ($query->execute() === 0) {
261
-				throw new TagNotFoundException(
262
-					'Tag does not exist', 0, null, [$tagId]
263
-				);
264
-			}
265
-		} catch (UniqueConstraintViolationException $e) {
266
-			throw new TagAlreadyExistsException(
267
-				'Tag ("' . $newName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
268
-				0,
269
-				$e
270
-			);
271
-		}
272
-
273
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent(
274
-			ManagerEvent::EVENT_UPDATE, $afterUpdate, $beforeUpdate
275
-		));
276
-	}
277
-
278
-	public function deleteTags($tagIds): void {
279
-		if (!\is_array($tagIds)) {
280
-			$tagIds = [$tagIds];
281
-		}
282
-
283
-		$tagNotFoundException = null;
284
-		$tags = [];
285
-		try {
286
-			$tags = $this->getTagsByIds($tagIds);
287
-		} catch (TagNotFoundException $e) {
288
-			$tagNotFoundException = $e;
289
-
290
-			// Get existing tag objects for the hooks later
291
-			$existingTags = array_diff($tagIds, $tagNotFoundException->getMissingTags());
292
-			if (!empty($existingTags)) {
293
-				try {
294
-					$tags = $this->getTagsByIds($existingTags);
295
-				} catch (TagNotFoundException $e) {
296
-					// Ignore further errors...
297
-				}
298
-			}
299
-		}
300
-
301
-		// delete relationships first
302
-		$query = $this->connection->getQueryBuilder();
303
-		$query->delete(SystemTagObjectMapper::RELATION_TABLE)
304
-			->where($query->expr()->in('systemtagid', $query->createParameter('tagids')))
305
-			->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
306
-			->execute();
307
-
308
-		$query = $this->connection->getQueryBuilder();
309
-		$query->delete(self::TAG_TABLE)
310
-			->where($query->expr()->in('id', $query->createParameter('tagids')))
311
-			->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
312
-			->execute();
313
-
314
-		foreach ($tags as $tag) {
315
-			$this->dispatcher->dispatch(ManagerEvent::EVENT_DELETE, new ManagerEvent(
316
-				ManagerEvent::EVENT_DELETE, $tag
317
-			));
318
-		}
319
-
320
-		if ($tagNotFoundException !== null) {
321
-			throw new TagNotFoundException(
322
-				'Tag id(s) not found', 0, $tagNotFoundException, $tagNotFoundException->getMissingTags()
323
-			);
324
-		}
325
-	}
326
-
327
-	public function canUserAssignTag(ISystemTag $tag, ?IUser $user): bool {
328
-		if ($user === null) {
329
-			return false;
330
-		}
331
-
332
-		// early check to avoid unneeded group lookups
333
-		if ($tag->isUserAssignable() && $tag->isUserVisible()) {
334
-			return true;
335
-		}
336
-
337
-		if ($this->groupManager->isAdmin($user->getUID())) {
338
-			return true;
339
-		}
340
-
341
-		if (!$tag->isUserVisible()) {
342
-			return false;
343
-		}
344
-
345
-		$groupIds = $this->groupManager->getUserGroupIds($user);
346
-		if (!empty($groupIds)) {
347
-			$matchingGroups = array_intersect($groupIds, $this->getTagGroups($tag));
348
-			if (!empty($matchingGroups)) {
349
-				return true;
350
-			}
351
-		}
352
-
353
-		return false;
354
-	}
355
-
356
-	public function canUserCreateTag(?IUser $user): bool {
357
-		if ($user === null) {
358
-			// If no user given, allows only calls from CLI
359
-			return \OC::$CLI;
360
-		}
361
-
362
-		if ($this->appConfig->getValueBool('systemtags', 'restrict_creation_to_admin', false) === false) {
363
-			return true;
364
-		}
365
-
366
-		return $this->groupManager->isAdmin($user->getUID());
367
-	}
368
-
369
-	public function canUserUpdateTag(?IUser $user): bool {
370
-		// We currently have no different permissions for updating tags than for creating them
371
-		return $this->canUserCreateTag($user);
372
-	}
373
-
374
-	public function canUserSeeTag(ISystemTag $tag, ?IUser $user): bool {
375
-		// If no user, then we only show public tags
376
-		if (!$user && $tag->getAccessLevel() === ISystemTag::ACCESS_LEVEL_PUBLIC) {
377
-			return true;
378
-		}
379
-
380
-		if ($tag->isUserVisible()) {
381
-			return true;
382
-		}
383
-
384
-		// if not returned yet, and user is not logged in, then the tag is not visible
385
-		if ($user === null) {
386
-			return false;
387
-		}
388
-
389
-		if ($this->groupManager->isAdmin($user->getUID())) {
390
-			return true;
391
-		}
392
-
393
-		return false;
394
-	}
395
-
396
-	private function createSystemTagFromRow($row): SystemTag {
397
-		return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable'], $row['etag'], $row['color']);
398
-	}
399
-
400
-	public function setTagGroups(ISystemTag $tag, array $groupIds): void {
401
-		// delete relationships first
402
-		$this->connection->beginTransaction();
403
-		try {
404
-			$query = $this->connection->getQueryBuilder();
405
-			$query->delete(self::TAG_GROUP_TABLE)
406
-				->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId())))
407
-				->execute();
408
-
409
-			// add each group id
410
-			$query = $this->connection->getQueryBuilder();
411
-			$query->insert(self::TAG_GROUP_TABLE)
412
-				->values([
413
-					'systemtagid' => $query->createNamedParameter($tag->getId()),
414
-					'gid' => $query->createParameter('gid'),
415
-				]);
416
-			foreach ($groupIds as $groupId) {
417
-				if ($groupId === '') {
418
-					continue;
419
-				}
420
-				$query->setParameter('gid', $groupId);
421
-				$query->execute();
422
-			}
423
-
424
-			$this->connection->commit();
425
-		} catch (\Exception $e) {
426
-			$this->connection->rollBack();
427
-			throw $e;
428
-		}
429
-	}
430
-
431
-	public function getTagGroups(ISystemTag $tag): array {
432
-		$groupIds = [];
433
-		$query = $this->connection->getQueryBuilder();
434
-		$query->select('gid')
435
-			->from(self::TAG_GROUP_TABLE)
436
-			->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId())))
437
-			->orderBy('gid');
438
-
439
-		$result = $query->execute();
440
-		while ($row = $result->fetch()) {
441
-			$groupIds[] = $row['gid'];
442
-		}
443
-
444
-		$result->closeCursor();
445
-
446
-		return $groupIds;
447
-	}
31
+    public const TAG_TABLE = 'systemtag';
32
+    public const TAG_GROUP_TABLE = 'systemtag_group';
33
+
34
+    /**
35
+     * Prepared query for selecting tags directly
36
+     */
37
+    private IQueryBuilder $selectTagQuery;
38
+
39
+    public function __construct(
40
+        protected IDBConnection $connection,
41
+        protected IGroupManager $groupManager,
42
+        protected IEventDispatcher $dispatcher,
43
+        private IUserSession $userSession,
44
+        private IAppConfig $appConfig,
45
+    ) {
46
+        $query = $this->connection->getQueryBuilder();
47
+        $this->selectTagQuery = $query->select('*')
48
+            ->from(self::TAG_TABLE)
49
+            ->where($query->expr()->eq('name', $query->createParameter('name')))
50
+            ->andWhere($query->expr()->eq('visibility', $query->createParameter('visibility')))
51
+            ->andWhere($query->expr()->eq('editable', $query->createParameter('editable')));
52
+    }
53
+
54
+    public function getTagsByIds($tagIds, ?IUser $user = null): array {
55
+        if (!\is_array($tagIds)) {
56
+            $tagIds = [$tagIds];
57
+        }
58
+
59
+        $tags = [];
60
+
61
+        // note: not all databases will fail if it's a string or starts with a number
62
+        foreach ($tagIds as $tagId) {
63
+            if (!is_numeric($tagId)) {
64
+                throw new \InvalidArgumentException('Tag id must be integer');
65
+            }
66
+        }
67
+
68
+        $query = $this->connection->getQueryBuilder();
69
+        $query->select('*')
70
+            ->from(self::TAG_TABLE)
71
+            ->where($query->expr()->in('id', $query->createParameter('tagids')))
72
+            ->addOrderBy('name', 'ASC')
73
+            ->addOrderBy('visibility', 'ASC')
74
+            ->addOrderBy('editable', 'ASC')
75
+            ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY);
76
+
77
+        $result = $query->execute();
78
+        while ($row = $result->fetch()) {
79
+            $tag = $this->createSystemTagFromRow($row);
80
+            if ($user && !$this->canUserSeeTag($tag, $user)) {
81
+                // if a user is given, hide invisible tags
82
+                continue;
83
+            }
84
+            $tags[$row['id']] = $tag;
85
+        }
86
+
87
+        $result->closeCursor();
88
+
89
+        if (\count($tags) !== \count($tagIds)) {
90
+            throw new TagNotFoundException(
91
+                'Tag id(s) not found', 0, null, array_diff($tagIds, array_keys($tags))
92
+            );
93
+        }
94
+
95
+        return $tags;
96
+    }
97
+
98
+    public function getAllTags($visibilityFilter = null, $nameSearchPattern = null): array {
99
+        $tags = [];
100
+
101
+        $query = $this->connection->getQueryBuilder();
102
+        $query->select('*')
103
+            ->from(self::TAG_TABLE);
104
+
105
+        if (!\is_null($visibilityFilter)) {
106
+            $query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int)$visibilityFilter)));
107
+        }
108
+
109
+        if (!empty($nameSearchPattern)) {
110
+            $query->andWhere(
111
+                $query->expr()->iLike(
112
+                    'name',
113
+                    $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern) . '%')
114
+                )
115
+            );
116
+        }
117
+
118
+        $query
119
+            ->addOrderBy('name', 'ASC')
120
+            ->addOrderBy('visibility', 'ASC')
121
+            ->addOrderBy('editable', 'ASC');
122
+
123
+        $result = $query->executeQuery();
124
+        while ($row = $result->fetch()) {
125
+            $tags[$row['id']] = $this->createSystemTagFromRow($row);
126
+        }
127
+
128
+        $result->closeCursor();
129
+
130
+        return $tags;
131
+    }
132
+
133
+    public function getTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
134
+        // Length of name column is 64
135
+        $truncatedTagName = substr($tagName, 0, 64);
136
+        $result = $this->selectTagQuery
137
+            ->setParameter('name', $truncatedTagName)
138
+            ->setParameter('visibility', $userVisible ? 1 : 0)
139
+            ->setParameter('editable', $userAssignable ? 1 : 0)
140
+            ->execute();
141
+
142
+        $row = $result->fetch();
143
+        $result->closeCursor();
144
+        if (!$row) {
145
+            throw new TagNotFoundException(
146
+                'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') does not exist'
147
+            );
148
+        }
149
+
150
+        return $this->createSystemTagFromRow($row);
151
+    }
152
+
153
+    public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag {
154
+        $user = $this->userSession->getUser();
155
+        if (!$this->canUserCreateTag($user)) {
156
+            throw new TagCreationForbiddenException();
157
+        }
158
+
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
+
167
+        // Length of name column is 64
168
+        $truncatedTagName = substr($tagName, 0, 64);
169
+        $query = $this->connection->getQueryBuilder();
170
+        $query->insert(self::TAG_TABLE)
171
+            ->values([
172
+                'name' => $query->createNamedParameter($truncatedTagName),
173
+                'visibility' => $query->createNamedParameter($userVisible ? 1 : 0),
174
+                'editable' => $query->createNamedParameter($userAssignable ? 1 : 0),
175
+                'etag' => $query->createNamedParameter(md5((string)time())),
176
+            ]);
177
+
178
+        try {
179
+            $query->execute();
180
+        } catch (UniqueConstraintViolationException $e) {
181
+            throw new TagAlreadyExistsException(
182
+                'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
183
+                0,
184
+                $e
185
+            );
186
+        }
187
+
188
+        $tagId = $query->getLastInsertId();
189
+
190
+        $tag = new SystemTag(
191
+            (string)$tagId,
192
+            $truncatedTagName,
193
+            $userVisible,
194
+            $userAssignable
195
+        );
196
+
197
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_CREATE, new ManagerEvent(
198
+            ManagerEvent::EVENT_CREATE, $tag
199
+        ));
200
+
201
+        return $tag;
202
+    }
203
+
204
+    public function updateTag(
205
+        string $tagId,
206
+        string $newName,
207
+        bool $userVisible,
208
+        bool $userAssignable,
209
+        ?string $color,
210
+    ): void {
211
+        try {
212
+            $tags = $this->getTagsByIds($tagId);
213
+        } catch (TagNotFoundException $e) {
214
+            throw new TagNotFoundException(
215
+                'Tag does not exist', 0, null, [$tagId]
216
+            );
217
+        }
218
+
219
+        $user = $this->userSession->getUser();
220
+        if (!$this->canUserUpdateTag($user)) {
221
+            throw new TagUpdateForbiddenException();
222
+        }
223
+
224
+        $beforeUpdate = array_shift($tags);
225
+        // Length of name column is 64
226
+        $newName = trim($newName);
227
+        $truncatedNewName = substr($newName, 0, 64);
228
+        $afterUpdate = new SystemTag(
229
+            $tagId,
230
+            $truncatedNewName,
231
+            $userVisible,
232
+            $userAssignable,
233
+            $beforeUpdate->getETag(),
234
+            $color
235
+        );
236
+
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
+
246
+        $query = $this->connection->getQueryBuilder();
247
+        $query->update(self::TAG_TABLE)
248
+            ->set('name', $query->createParameter('name'))
249
+            ->set('visibility', $query->createParameter('visibility'))
250
+            ->set('editable', $query->createParameter('editable'))
251
+            ->set('color', $query->createParameter('color'))
252
+            ->where($query->expr()->eq('id', $query->createParameter('tagid')))
253
+            ->setParameter('name', $truncatedNewName)
254
+            ->setParameter('visibility', $userVisible ? 1 : 0)
255
+            ->setParameter('editable', $userAssignable ? 1 : 0)
256
+            ->setParameter('tagid', $tagId)
257
+            ->setParameter('color', $color);
258
+
259
+        try {
260
+            if ($query->execute() === 0) {
261
+                throw new TagNotFoundException(
262
+                    'Tag does not exist', 0, null, [$tagId]
263
+                );
264
+            }
265
+        } catch (UniqueConstraintViolationException $e) {
266
+            throw new TagAlreadyExistsException(
267
+                'Tag ("' . $newName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
268
+                0,
269
+                $e
270
+            );
271
+        }
272
+
273
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent(
274
+            ManagerEvent::EVENT_UPDATE, $afterUpdate, $beforeUpdate
275
+        ));
276
+    }
277
+
278
+    public function deleteTags($tagIds): void {
279
+        if (!\is_array($tagIds)) {
280
+            $tagIds = [$tagIds];
281
+        }
282
+
283
+        $tagNotFoundException = null;
284
+        $tags = [];
285
+        try {
286
+            $tags = $this->getTagsByIds($tagIds);
287
+        } catch (TagNotFoundException $e) {
288
+            $tagNotFoundException = $e;
289
+
290
+            // Get existing tag objects for the hooks later
291
+            $existingTags = array_diff($tagIds, $tagNotFoundException->getMissingTags());
292
+            if (!empty($existingTags)) {
293
+                try {
294
+                    $tags = $this->getTagsByIds($existingTags);
295
+                } catch (TagNotFoundException $e) {
296
+                    // Ignore further errors...
297
+                }
298
+            }
299
+        }
300
+
301
+        // delete relationships first
302
+        $query = $this->connection->getQueryBuilder();
303
+        $query->delete(SystemTagObjectMapper::RELATION_TABLE)
304
+            ->where($query->expr()->in('systemtagid', $query->createParameter('tagids')))
305
+            ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
306
+            ->execute();
307
+
308
+        $query = $this->connection->getQueryBuilder();
309
+        $query->delete(self::TAG_TABLE)
310
+            ->where($query->expr()->in('id', $query->createParameter('tagids')))
311
+            ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
312
+            ->execute();
313
+
314
+        foreach ($tags as $tag) {
315
+            $this->dispatcher->dispatch(ManagerEvent::EVENT_DELETE, new ManagerEvent(
316
+                ManagerEvent::EVENT_DELETE, $tag
317
+            ));
318
+        }
319
+
320
+        if ($tagNotFoundException !== null) {
321
+            throw new TagNotFoundException(
322
+                'Tag id(s) not found', 0, $tagNotFoundException, $tagNotFoundException->getMissingTags()
323
+            );
324
+        }
325
+    }
326
+
327
+    public function canUserAssignTag(ISystemTag $tag, ?IUser $user): bool {
328
+        if ($user === null) {
329
+            return false;
330
+        }
331
+
332
+        // early check to avoid unneeded group lookups
333
+        if ($tag->isUserAssignable() && $tag->isUserVisible()) {
334
+            return true;
335
+        }
336
+
337
+        if ($this->groupManager->isAdmin($user->getUID())) {
338
+            return true;
339
+        }
340
+
341
+        if (!$tag->isUserVisible()) {
342
+            return false;
343
+        }
344
+
345
+        $groupIds = $this->groupManager->getUserGroupIds($user);
346
+        if (!empty($groupIds)) {
347
+            $matchingGroups = array_intersect($groupIds, $this->getTagGroups($tag));
348
+            if (!empty($matchingGroups)) {
349
+                return true;
350
+            }
351
+        }
352
+
353
+        return false;
354
+    }
355
+
356
+    public function canUserCreateTag(?IUser $user): bool {
357
+        if ($user === null) {
358
+            // If no user given, allows only calls from CLI
359
+            return \OC::$CLI;
360
+        }
361
+
362
+        if ($this->appConfig->getValueBool('systemtags', 'restrict_creation_to_admin', false) === false) {
363
+            return true;
364
+        }
365
+
366
+        return $this->groupManager->isAdmin($user->getUID());
367
+    }
368
+
369
+    public function canUserUpdateTag(?IUser $user): bool {
370
+        // We currently have no different permissions for updating tags than for creating them
371
+        return $this->canUserCreateTag($user);
372
+    }
373
+
374
+    public function canUserSeeTag(ISystemTag $tag, ?IUser $user): bool {
375
+        // If no user, then we only show public tags
376
+        if (!$user && $tag->getAccessLevel() === ISystemTag::ACCESS_LEVEL_PUBLIC) {
377
+            return true;
378
+        }
379
+
380
+        if ($tag->isUserVisible()) {
381
+            return true;
382
+        }
383
+
384
+        // if not returned yet, and user is not logged in, then the tag is not visible
385
+        if ($user === null) {
386
+            return false;
387
+        }
388
+
389
+        if ($this->groupManager->isAdmin($user->getUID())) {
390
+            return true;
391
+        }
392
+
393
+        return false;
394
+    }
395
+
396
+    private function createSystemTagFromRow($row): SystemTag {
397
+        return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable'], $row['etag'], $row['color']);
398
+    }
399
+
400
+    public function setTagGroups(ISystemTag $tag, array $groupIds): void {
401
+        // delete relationships first
402
+        $this->connection->beginTransaction();
403
+        try {
404
+            $query = $this->connection->getQueryBuilder();
405
+            $query->delete(self::TAG_GROUP_TABLE)
406
+                ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId())))
407
+                ->execute();
408
+
409
+            // add each group id
410
+            $query = $this->connection->getQueryBuilder();
411
+            $query->insert(self::TAG_GROUP_TABLE)
412
+                ->values([
413
+                    'systemtagid' => $query->createNamedParameter($tag->getId()),
414
+                    'gid' => $query->createParameter('gid'),
415
+                ]);
416
+            foreach ($groupIds as $groupId) {
417
+                if ($groupId === '') {
418
+                    continue;
419
+                }
420
+                $query->setParameter('gid', $groupId);
421
+                $query->execute();
422
+            }
423
+
424
+            $this->connection->commit();
425
+        } catch (\Exception $e) {
426
+            $this->connection->rollBack();
427
+            throw $e;
428
+        }
429
+    }
430
+
431
+    public function getTagGroups(ISystemTag $tag): array {
432
+        $groupIds = [];
433
+        $query = $this->connection->getQueryBuilder();
434
+        $query->select('gid')
435
+            ->from(self::TAG_GROUP_TABLE)
436
+            ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tag->getId())))
437
+            ->orderBy('gid');
438
+
439
+        $result = $query->execute();
440
+        while ($row = $result->fetch()) {
441
+            $groupIds[] = $row['gid'];
442
+        }
443
+
444
+        $result->closeCursor();
445
+
446
+        return $groupIds;
447
+    }
448 448
 
449 449
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -103,14 +103,14 @@  discard block
 block discarded – undo
103 103
 			->from(self::TAG_TABLE);
104 104
 
105 105
 		if (!\is_null($visibilityFilter)) {
106
-			$query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int)$visibilityFilter)));
106
+			$query->andWhere($query->expr()->eq('visibility', $query->createNamedParameter((int) $visibilityFilter)));
107 107
 		}
108 108
 
109 109
 		if (!empty($nameSearchPattern)) {
110 110
 			$query->andWhere(
111 111
 				$query->expr()->iLike(
112 112
 					'name',
113
-					$query->createNamedParameter('%' . $this->connection->escapeLikeParameter($nameSearchPattern) . '%')
113
+					$query->createNamedParameter('%'.$this->connection->escapeLikeParameter($nameSearchPattern).'%')
114 114
 				)
115 115
 			);
116 116
 		}
@@ -143,7 +143,7 @@  discard block
 block discarded – undo
143 143
 		$result->closeCursor();
144 144
 		if (!$row) {
145 145
 			throw new TagNotFoundException(
146
-				'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') does not exist'
146
+				'Tag ("'.$truncatedTagName.'", '.$userVisible.', '.$userAssignable.') does not exist'
147 147
 			);
148 148
 		}
149 149
 
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
 		$existingTags = $this->getAllTags(null, $tagName);
161 161
 		foreach ($existingTags as $existingTag) {
162 162
 			if (mb_strtolower($existingTag->getName()) === mb_strtolower($tagName)) {
163
-				throw new TagAlreadyExistsException('Tag ' . $tagName . ' already exists');
163
+				throw new TagAlreadyExistsException('Tag '.$tagName.' already exists');
164 164
 			}
165 165
 		}
166 166
 
@@ -172,14 +172,14 @@  discard block
 block discarded – undo
172 172
 				'name' => $query->createNamedParameter($truncatedTagName),
173 173
 				'visibility' => $query->createNamedParameter($userVisible ? 1 : 0),
174 174
 				'editable' => $query->createNamedParameter($userAssignable ? 1 : 0),
175
-				'etag' => $query->createNamedParameter(md5((string)time())),
175
+				'etag' => $query->createNamedParameter(md5((string) time())),
176 176
 			]);
177 177
 
178 178
 		try {
179 179
 			$query->execute();
180 180
 		} catch (UniqueConstraintViolationException $e) {
181 181
 			throw new TagAlreadyExistsException(
182
-				'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
182
+				'Tag ("'.$truncatedTagName.'", '.$userVisible.', '.$userAssignable.') already exists',
183 183
 				0,
184 184
 				$e
185 185
 			);
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
 		$tagId = $query->getLastInsertId();
189 189
 
190 190
 		$tag = new SystemTag(
191
-			(string)$tagId,
191
+			(string) $tagId,
192 192
 			$truncatedTagName,
193 193
 			$userVisible,
194 194
 			$userAssignable
@@ -239,7 +239,7 @@  discard block
 block discarded – undo
239 239
 		foreach ($existingTags as $existingTag) {
240 240
 			if (mb_strtolower($existingTag->getName()) === mb_strtolower($truncatedNewName)
241 241
 				&& $existingTag->getId() !== $tagId) {
242
-				throw new TagAlreadyExistsException('Tag ' . $truncatedNewName . ' already exists');
242
+				throw new TagAlreadyExistsException('Tag '.$truncatedNewName.' already exists');
243 243
 			}
244 244
 		}
245 245
 
@@ -264,7 +264,7 @@  discard block
 block discarded – undo
264 264
 			}
265 265
 		} catch (UniqueConstraintViolationException $e) {
266 266
 			throw new TagAlreadyExistsException(
267
-				'Tag ("' . $newName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
267
+				'Tag ("'.$newName.'", '.$userVisible.', '.$userAssignable.') already exists',
268 268
 				0,
269 269
 				$e
270 270
 			);
@@ -394,7 +394,7 @@  discard block
 block discarded – undo
394 394
 	}
395 395
 
396 396
 	private function createSystemTagFromRow($row): SystemTag {
397
-		return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable'], $row['etag'], $row['color']);
397
+		return new SystemTag((string) $row['id'], $row['name'], (bool) $row['visibility'], (bool) $row['editable'], $row['etag'], $row['color']);
398 398
 	}
399 399
 
400 400
 	public function setTagGroups(ISystemTag $tag, array $groupIds): void {
Please login to merge, or discard this patch.