Completed
Push — master ( 405069...7bd0b5 )
by Marcel
31:44
created
lib/public/SystemTag/TagAssignedEvent.php 1 patch
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -16,58 +16,58 @@
 block discarded – undo
16 16
  * @since 32.0.0
17 17
  */
18 18
 class TagAssignedEvent extends Event implements IWebhookCompatibleEvent {
19
-	protected string $objectType;
20
-	/** @var list<string> */
21
-	protected array $objectIds;
22
-	/** @var list<int> */
23
-	protected array $tags;
19
+    protected string $objectType;
20
+    /** @var list<string> */
21
+    protected array $objectIds;
22
+    /** @var list<int> */
23
+    protected array $tags;
24 24
 
25
-	/**
26
-	 * constructor
27
-	 *
28
-	 * @param list<string> $objectIds
29
-	 * @param list<int> $tags
30
-	 * @since 32.0.0
31
-	 */
32
-	public function __construct(string $objectType, array $objectIds, array $tags) {
33
-		parent::__construct();
34
-		$this->objectType = $objectType;
35
-		$this->objectIds = $objectIds;
36
-		$this->tags = $tags;
37
-	}
25
+    /**
26
+     * constructor
27
+     *
28
+     * @param list<string> $objectIds
29
+     * @param list<int> $tags
30
+     * @since 32.0.0
31
+     */
32
+    public function __construct(string $objectType, array $objectIds, array $tags) {
33
+        parent::__construct();
34
+        $this->objectType = $objectType;
35
+        $this->objectIds = $objectIds;
36
+        $this->tags = $tags;
37
+    }
38 38
 
39
-	/**
40
-	 * @since 32.0.0
41
-	 */
42
-	public function getObjectType(): string {
43
-		return $this->objectType;
44
-	}
39
+    /**
40
+     * @since 32.0.0
41
+     */
42
+    public function getObjectType(): string {
43
+        return $this->objectType;
44
+    }
45 45
 
46
-	/**
47
-	 * @return list<string>
48
-	 * @since 32.0.0
49
-	 */
50
-	public function getObjectIds(): array {
51
-		return $this->objectIds;
52
-	}
46
+    /**
47
+     * @return list<string>
48
+     * @since 32.0.0
49
+     */
50
+    public function getObjectIds(): array {
51
+        return $this->objectIds;
52
+    }
53 53
 
54
-	/**
55
-	 * @return list<int>
56
-	 * @since 32.0.0
57
-	 */
58
-	public function getTags(): array {
59
-		return $this->tags;
60
-	}
54
+    /**
55
+     * @return list<int>
56
+     * @since 32.0.0
57
+     */
58
+    public function getTags(): array {
59
+        return $this->tags;
60
+    }
61 61
 
62
-	/**
63
-	 * @return array{objectType: string, objectIds: list<string>, tagIds: list<int>}
64
-	 * @since 32.0.0
65
-	 */
66
-	public function getWebhookSerializable(): array {
67
-		return [
68
-			'objectType' => $this->getObjectType(),
69
-			'objectIds' => $this->getObjectIds(),
70
-			'tagIds' => $this->getTags(),
71
-		];
72
-	}
62
+    /**
63
+     * @return array{objectType: string, objectIds: list<string>, tagIds: list<int>}
64
+     * @since 32.0.0
65
+     */
66
+    public function getWebhookSerializable(): array {
67
+        return [
68
+            'objectType' => $this->getObjectType(),
69
+            'objectIds' => $this->getObjectIds(),
70
+            'tagIds' => $this->getTags(),
71
+        ];
72
+    }
73 73
 }
Please login to merge, or discard this patch.
lib/public/SystemTag/TagUnassignedEvent.php 1 patch
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -16,58 +16,58 @@
 block discarded – undo
16 16
  * @since 32.0.0
17 17
  */
18 18
 class TagUnassignedEvent extends Event implements IWebhookCompatibleEvent {
19
-	protected string $objectType;
20
-	/** @var list<string> */
21
-	protected array $objectIds;
22
-	/** @var list<int> */
23
-	protected array $tags;
19
+    protected string $objectType;
20
+    /** @var list<string> */
21
+    protected array $objectIds;
22
+    /** @var list<int> */
23
+    protected array $tags;
24 24
 
25
-	/**
26
-	 * constructor
27
-	 *
28
-	 * @param list<string> $objectIds
29
-	 * @param list<int> $tags
30
-	 * @since 32.0.0
31
-	 */
32
-	public function __construct(string $objectType, array $objectIds, array $tags) {
33
-		parent::__construct();
34
-		$this->objectType = $objectType;
35
-		$this->objectIds = $objectIds;
36
-		$this->tags = $tags;
37
-	}
25
+    /**
26
+     * constructor
27
+     *
28
+     * @param list<string> $objectIds
29
+     * @param list<int> $tags
30
+     * @since 32.0.0
31
+     */
32
+    public function __construct(string $objectType, array $objectIds, array $tags) {
33
+        parent::__construct();
34
+        $this->objectType = $objectType;
35
+        $this->objectIds = $objectIds;
36
+        $this->tags = $tags;
37
+    }
38 38
 
39
-	/**
40
-	 * @since 32.0.0
41
-	 */
42
-	public function getObjectType(): string {
43
-		return $this->objectType;
44
-	}
39
+    /**
40
+     * @since 32.0.0
41
+     */
42
+    public function getObjectType(): string {
43
+        return $this->objectType;
44
+    }
45 45
 
46
-	/**
47
-	 * @return list<string>
48
-	 * @since 32.0.0
49
-	 */
50
-	public function getObjectIds(): array {
51
-		return $this->objectIds;
52
-	}
46
+    /**
47
+     * @return list<string>
48
+     * @since 32.0.0
49
+     */
50
+    public function getObjectIds(): array {
51
+        return $this->objectIds;
52
+    }
53 53
 
54
-	/**
55
-	 * @return list<int>
56
-	 * @since 32.0.0
57
-	 */
58
-	public function getTags(): array {
59
-		return $this->tags;
60
-	}
54
+    /**
55
+     * @return list<int>
56
+     * @since 32.0.0
57
+     */
58
+    public function getTags(): array {
59
+        return $this->tags;
60
+    }
61 61
 
62
-	/**
63
-	 * @return array{objectType: string, objectIds: list<string>, tagIds: list<int>}
64
-	 * @since 32.0.0
65
-	 */
66
-	public function getWebhookSerializable(): array {
67
-		return [
68
-			'objectType' => $this->getObjectType(),
69
-			'objectIds' => $this->getObjectIds(),
70
-			'tagIds' => $this->getTags(),
71
-		];
72
-	}
62
+    /**
63
+     * @return array{objectType: string, objectIds: list<string>, tagIds: list<int>}
64
+     * @since 32.0.0
65
+     */
66
+    public function getWebhookSerializable(): array {
67
+        return [
68
+            'objectType' => $this->getObjectType(),
69
+            'objectIds' => $this->getObjectIds(),
70
+            'tagIds' => $this->getTags(),
71
+        ];
72
+    }
73 73
 }
Please login to merge, or discard this patch.
lib/private/SystemTag/SystemTagObjectMapper.php 1 patch
Indentation   +355 added lines, -355 removed lines patch added patch discarded remove patch
@@ -22,359 +22,359 @@
 block discarded – undo
22 22
 use OCP\SystemTag\TagUnassignedEvent;
23 23
 
24 24
 class SystemTagObjectMapper implements ISystemTagObjectMapper {
25
-	public const RELATION_TABLE = 'systemtag_object_mapping';
26
-
27
-	public function __construct(
28
-		protected IDBConnection $connection,
29
-		protected ISystemTagManager $tagManager,
30
-		protected IEventDispatcher $dispatcher,
31
-	) {
32
-	}
33
-
34
-	/**
35
-	 * {@inheritdoc}
36
-	 */
37
-	public function getTagIdsForObjects($objIds, string $objectType): array {
38
-		if (!\is_array($objIds)) {
39
-			$objIds = [$objIds];
40
-		} elseif (empty($objIds)) {
41
-			return [];
42
-		}
43
-
44
-		$query = $this->connection->getQueryBuilder();
45
-		$query->select(['systemtagid', 'objectid'])
46
-			->from(self::RELATION_TABLE)
47
-			->where($query->expr()->in('objectid', $query->createParameter('objectids')))
48
-			->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
49
-			->setParameter('objecttype', $objectType)
50
-			->addOrderBy('objectid', 'ASC')
51
-			->addOrderBy('systemtagid', 'ASC');
52
-		$chunks = array_chunk($objIds, 900, false);
53
-		$mapping = [];
54
-		foreach ($objIds as $objId) {
55
-			$mapping[$objId] = [];
56
-		}
57
-		foreach ($chunks as $chunk) {
58
-			$query->setParameter('objectids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
59
-			$result = $query->executeQuery();
60
-			while ($row = $result->fetch()) {
61
-				$objectId = $row['objectid'];
62
-				$mapping[$objectId][] = $row['systemtagid'];
63
-			}
64
-
65
-			$result->closeCursor();
66
-		}
67
-
68
-		return $mapping;
69
-	}
70
-
71
-	/**
72
-	 * {@inheritdoc}
73
-	 */
74
-	public function getObjectIdsForTags($tagIds, string $objectType, int $limit = 0, string $offset = ''): array {
75
-		if (!\is_array($tagIds)) {
76
-			$tagIds = [$tagIds];
77
-		}
78
-
79
-		$this->assertTagsExist($tagIds);
80
-
81
-		$query = $this->connection->getQueryBuilder();
82
-		$query->selectDistinct('objectid')
83
-			->from(self::RELATION_TABLE)
84
-			->where($query->expr()->in('systemtagid', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)))
85
-			->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)));
86
-
87
-		if ($limit) {
88
-			if (\count($tagIds) !== 1) {
89
-				throw new \InvalidArgumentException('Limit is only allowed with a single tag');
90
-			}
91
-
92
-			$query->setMaxResults($limit)
93
-				->orderBy('objectid', 'ASC');
94
-
95
-			if ($offset !== '') {
96
-				$query->andWhere($query->expr()->gt('objectid', $query->createNamedParameter($offset)));
97
-			}
98
-		}
99
-
100
-		$objectIds = [];
101
-
102
-		$result = $query->executeQuery();
103
-		while ($row = $result->fetch()) {
104
-			$objectIds[] = $row['objectid'];
105
-		}
106
-		$result->closeCursor();
107
-
108
-		return $objectIds;
109
-	}
110
-
111
-	/**
112
-	 * {@inheritdoc}
113
-	 */
114
-	public function assignTags(string $objId, string $objectType, $tagIds): void {
115
-		if (!\is_array($tagIds)) {
116
-			$tagIds = [$tagIds];
117
-		}
118
-
119
-		$this->assertTagsExist($tagIds);
120
-		$this->connection->beginTransaction();
121
-
122
-		$query = $this->connection->getQueryBuilder();
123
-		$query->select('systemtagid')
124
-			->from(self::RELATION_TABLE)
125
-			->where($query->expr()->in('systemtagid', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)))
126
-			->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)))
127
-			->andWhere($query->expr()->eq('objectid', $query->createNamedParameter($objId)));
128
-		$result = $query->executeQuery();
129
-		$rows = $result->fetchAll();
130
-		$existingTags = [];
131
-		foreach ($rows as $row) {
132
-			$existingTags[] = $row['systemtagid'];
133
-		}
134
-		//filter only tags that do not exist in db
135
-		$tagIds = array_diff($tagIds, $existingTags);
136
-		if (empty($tagIds)) {
137
-			// no tags to insert so return here
138
-			$this->connection->commit();
139
-			return;
140
-		}
141
-
142
-		$query = $this->connection->getQueryBuilder();
143
-		$query->insert(self::RELATION_TABLE)
144
-			->values([
145
-				'objectid' => $query->createNamedParameter($objId),
146
-				'objecttype' => $query->createNamedParameter($objectType),
147
-				'systemtagid' => $query->createParameter('tagid'),
148
-			]);
149
-
150
-		$tagsAssigned = [];
151
-		foreach ($tagIds as $tagId) {
152
-			try {
153
-				$query->setParameter('tagid', $tagId);
154
-				$query->executeStatement();
155
-				$tagsAssigned[] = $tagId;
156
-			} catch (Exception $e) {
157
-				if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
158
-					throw $e;
159
-				}
160
-				// ignore existing relations
161
-			}
162
-		}
163
-
164
-		$this->updateEtagForTags($tagIds);
165
-
166
-		$this->connection->commit();
167
-		if (empty($tagsAssigned)) {
168
-			return;
169
-		}
170
-
171
-		$this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent(
172
-			MapperEvent::EVENT_ASSIGN,
173
-			$objectType,
174
-			$objId,
175
-			$tagsAssigned
176
-		));
177
-		$this->dispatcher->dispatchTyped(new TagAssignedEvent($objectType, [$objId], $tagsAssigned));
178
-	}
179
-
180
-	/**
181
-	 * {@inheritdoc}
182
-	 */
183
-	public function unassignTags(string $objId, string $objectType, $tagIds): void {
184
-		if (!\is_array($tagIds)) {
185
-			$tagIds = [$tagIds];
186
-		}
187
-
188
-		$this->assertTagsExist($tagIds);
189
-
190
-		$query = $this->connection->getQueryBuilder();
191
-		$query->delete(self::RELATION_TABLE)
192
-			->where($query->expr()->eq('objectid', $query->createParameter('objectid')))
193
-			->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
194
-			->andWhere($query->expr()->in('systemtagid', $query->createParameter('tagids')))
195
-			->setParameter('objectid', $objId)
196
-			->setParameter('objecttype', $objectType)
197
-			->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
198
-			->executeStatement();
199
-
200
-		$this->updateEtagForTags($tagIds);
201
-
202
-		$this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
203
-			MapperEvent::EVENT_UNASSIGN,
204
-			$objectType,
205
-			$objId,
206
-			$tagIds
207
-		));
208
-		$this->dispatcher->dispatchTyped(new TagUnassignedEvent($objectType, [$objId], $tagIds));
209
-	}
210
-
211
-	/**
212
-	 * Update the etag for the given tags.
213
-	 *
214
-	 * @param string[] $tagIds
215
-	 */
216
-	private function updateEtagForTags(array $tagIds): void {
217
-		// Update etag after assigning tags
218
-		$md5 = md5(json_encode(time()));
219
-		$query = $this->connection->getQueryBuilder();
220
-		$query->update('systemtag')
221
-			->set('etag', $query->createNamedParameter($md5))
222
-			->where($query->expr()->in('id', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)));
223
-		$query->executeStatement();
224
-	}
225
-
226
-	/**
227
-	 * {@inheritdoc}
228
-	 */
229
-	public function haveTag($objIds, string $objectType, string $tagId, bool $all = true): bool {
230
-		$this->assertTagsExist([$tagId]);
231
-
232
-		if (!\is_array($objIds)) {
233
-			$objIds = [$objIds];
234
-		}
235
-
236
-		$query = $this->connection->getQueryBuilder();
237
-
238
-		if (!$all) {
239
-			// If we only need one entry, we make the query lighter, by not
240
-			// counting the elements
241
-			$query->select('*')
242
-				->setMaxResults(1);
243
-		} else {
244
-			$query->select($query->func()->count($query->expr()->literal(1)));
245
-		}
246
-
247
-		$query->from(self::RELATION_TABLE)
248
-			->where($query->expr()->in('objectid', $query->createParameter('objectids')))
249
-			->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
250
-			->andWhere($query->expr()->eq('systemtagid', $query->createParameter('tagid')))
251
-			->setParameter('objectids', $objIds, IQueryBuilder::PARAM_STR_ARRAY)
252
-			->setParameter('tagid', $tagId)
253
-			->setParameter('objecttype', $objectType);
254
-
255
-		$result = $query->executeQuery();
256
-		$row = $result->fetch(\PDO::FETCH_NUM);
257
-		$result->closeCursor();
258
-
259
-		if ($all) {
260
-			return ((int)$row[0] === \count($objIds));
261
-		}
262
-
263
-		return (bool)$row;
264
-	}
265
-
266
-	/**
267
-	 * Asserts that all the given tag ids exist.
268
-	 *
269
-	 * @param string[] $tagIds tag ids to check
270
-	 *
271
-	 * @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist
272
-	 */
273
-	private function assertTagsExist(array $tagIds): void {
274
-		$tags = $this->tagManager->getTagsByIds($tagIds);
275
-		if (\count($tags) !== \count($tagIds)) {
276
-			// at least one tag missing, bail out
277
-			$foundTagIds = array_map(
278
-				function (ISystemTag $tag) {
279
-					return $tag->getId();
280
-				},
281
-				$tags
282
-			);
283
-			$missingTagIds = array_diff($tagIds, $foundTagIds);
284
-			throw new TagNotFoundException(
285
-				'Tags not found', 0, null, $missingTagIds
286
-			);
287
-		}
288
-	}
289
-
290
-	/**
291
-	 * {@inheritdoc}
292
-	 */
293
-	public function setObjectIdsForTag(string $tagId, string $objectType, array $objectIds): void {
294
-		$currentObjectIds = $this->getObjectIdsForTags($tagId, $objectType);
295
-		$removedObjectIds = array_diff($currentObjectIds, $objectIds);
296
-		$addedObjectIds = array_diff($objectIds, $currentObjectIds);
297
-
298
-		$this->connection->beginTransaction();
299
-		$query = $this->connection->getQueryBuilder();
300
-		$query->delete(self::RELATION_TABLE)
301
-			->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tagId, IQueryBuilder::PARAM_INT)))
302
-			->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)))
303
-			->executeStatement();
304
-		$this->connection->commit();
305
-
306
-		foreach ($removedObjectIds as $objectId) {
307
-			$this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
308
-				MapperEvent::EVENT_UNASSIGN,
309
-				$objectType,
310
-				(string)$objectId,
311
-				[(int)$tagId]
312
-			));
313
-		}
314
-		if (!empty($removedObjectIds)) {
315
-			$this->dispatcher->dispatchTyped(new TagUnassignedEvent($objectType, array_map(fn ($objectId) => (string)$objectId, $removedObjectIds), [(int)$tagId]));
316
-		}
317
-
318
-		if (empty($objectIds)) {
319
-			return;
320
-		}
321
-
322
-		$this->connection->beginTransaction();
323
-		$query = $this->connection->getQueryBuilder();
324
-		$query->insert(self::RELATION_TABLE)
325
-			->values([
326
-				'systemtagid' => $query->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
327
-				'objecttype' => $query->createNamedParameter($objectType),
328
-				'objectid' => $query->createParameter('objectid'),
329
-			]);
330
-
331
-		foreach (array_unique($objectIds) as $objectId) {
332
-			$query->setParameter('objectid', (string)$objectId);
333
-			$query->executeStatement();
334
-		}
335
-
336
-		$this->updateEtagForTags([$tagId]);
337
-		$this->connection->commit();
338
-
339
-		// Dispatch assign events for new object ids
340
-		foreach ($addedObjectIds as $objectId) {
341
-			$this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent(
342
-				MapperEvent::EVENT_ASSIGN,
343
-				$objectType,
344
-				(string)$objectId,
345
-				[(int)$tagId]
346
-			));
347
-		}
348
-		if (!empty($addedObjectIds)) {
349
-			$this->dispatcher->dispatchTyped(new TagAssignedEvent($objectType, array_map(fn ($objectId) => (string)$objectId, $addedObjectIds), [(int)$tagId]));
350
-		}
351
-
352
-		// Dispatch unassign events for removed object ids
353
-		foreach ($removedObjectIds as $objectId) {
354
-			$this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
355
-				MapperEvent::EVENT_UNASSIGN,
356
-				$objectType,
357
-				(string)$objectId,
358
-				[(int)$tagId]
359
-			));
360
-		}
361
-	}
362
-
363
-	/**
364
-	 * {@inheritdoc}
365
-	 */
366
-	public function getAvailableObjectTypes(): array {
367
-		$query = $this->connection->getQueryBuilder();
368
-		$query->selectDistinct('objecttype')
369
-			->from(self::RELATION_TABLE);
370
-
371
-		$result = $query->executeQuery();
372
-		$objectTypes = [];
373
-		while ($row = $result->fetch()) {
374
-			$objectTypes[] = $row['objecttype'];
375
-		}
376
-		$result->closeCursor();
377
-
378
-		return $objectTypes;
379
-	}
25
+    public const RELATION_TABLE = 'systemtag_object_mapping';
26
+
27
+    public function __construct(
28
+        protected IDBConnection $connection,
29
+        protected ISystemTagManager $tagManager,
30
+        protected IEventDispatcher $dispatcher,
31
+    ) {
32
+    }
33
+
34
+    /**
35
+     * {@inheritdoc}
36
+     */
37
+    public function getTagIdsForObjects($objIds, string $objectType): array {
38
+        if (!\is_array($objIds)) {
39
+            $objIds = [$objIds];
40
+        } elseif (empty($objIds)) {
41
+            return [];
42
+        }
43
+
44
+        $query = $this->connection->getQueryBuilder();
45
+        $query->select(['systemtagid', 'objectid'])
46
+            ->from(self::RELATION_TABLE)
47
+            ->where($query->expr()->in('objectid', $query->createParameter('objectids')))
48
+            ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
49
+            ->setParameter('objecttype', $objectType)
50
+            ->addOrderBy('objectid', 'ASC')
51
+            ->addOrderBy('systemtagid', 'ASC');
52
+        $chunks = array_chunk($objIds, 900, false);
53
+        $mapping = [];
54
+        foreach ($objIds as $objId) {
55
+            $mapping[$objId] = [];
56
+        }
57
+        foreach ($chunks as $chunk) {
58
+            $query->setParameter('objectids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
59
+            $result = $query->executeQuery();
60
+            while ($row = $result->fetch()) {
61
+                $objectId = $row['objectid'];
62
+                $mapping[$objectId][] = $row['systemtagid'];
63
+            }
64
+
65
+            $result->closeCursor();
66
+        }
67
+
68
+        return $mapping;
69
+    }
70
+
71
+    /**
72
+     * {@inheritdoc}
73
+     */
74
+    public function getObjectIdsForTags($tagIds, string $objectType, int $limit = 0, string $offset = ''): array {
75
+        if (!\is_array($tagIds)) {
76
+            $tagIds = [$tagIds];
77
+        }
78
+
79
+        $this->assertTagsExist($tagIds);
80
+
81
+        $query = $this->connection->getQueryBuilder();
82
+        $query->selectDistinct('objectid')
83
+            ->from(self::RELATION_TABLE)
84
+            ->where($query->expr()->in('systemtagid', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)))
85
+            ->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)));
86
+
87
+        if ($limit) {
88
+            if (\count($tagIds) !== 1) {
89
+                throw new \InvalidArgumentException('Limit is only allowed with a single tag');
90
+            }
91
+
92
+            $query->setMaxResults($limit)
93
+                ->orderBy('objectid', 'ASC');
94
+
95
+            if ($offset !== '') {
96
+                $query->andWhere($query->expr()->gt('objectid', $query->createNamedParameter($offset)));
97
+            }
98
+        }
99
+
100
+        $objectIds = [];
101
+
102
+        $result = $query->executeQuery();
103
+        while ($row = $result->fetch()) {
104
+            $objectIds[] = $row['objectid'];
105
+        }
106
+        $result->closeCursor();
107
+
108
+        return $objectIds;
109
+    }
110
+
111
+    /**
112
+     * {@inheritdoc}
113
+     */
114
+    public function assignTags(string $objId, string $objectType, $tagIds): void {
115
+        if (!\is_array($tagIds)) {
116
+            $tagIds = [$tagIds];
117
+        }
118
+
119
+        $this->assertTagsExist($tagIds);
120
+        $this->connection->beginTransaction();
121
+
122
+        $query = $this->connection->getQueryBuilder();
123
+        $query->select('systemtagid')
124
+            ->from(self::RELATION_TABLE)
125
+            ->where($query->expr()->in('systemtagid', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)))
126
+            ->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)))
127
+            ->andWhere($query->expr()->eq('objectid', $query->createNamedParameter($objId)));
128
+        $result = $query->executeQuery();
129
+        $rows = $result->fetchAll();
130
+        $existingTags = [];
131
+        foreach ($rows as $row) {
132
+            $existingTags[] = $row['systemtagid'];
133
+        }
134
+        //filter only tags that do not exist in db
135
+        $tagIds = array_diff($tagIds, $existingTags);
136
+        if (empty($tagIds)) {
137
+            // no tags to insert so return here
138
+            $this->connection->commit();
139
+            return;
140
+        }
141
+
142
+        $query = $this->connection->getQueryBuilder();
143
+        $query->insert(self::RELATION_TABLE)
144
+            ->values([
145
+                'objectid' => $query->createNamedParameter($objId),
146
+                'objecttype' => $query->createNamedParameter($objectType),
147
+                'systemtagid' => $query->createParameter('tagid'),
148
+            ]);
149
+
150
+        $tagsAssigned = [];
151
+        foreach ($tagIds as $tagId) {
152
+            try {
153
+                $query->setParameter('tagid', $tagId);
154
+                $query->executeStatement();
155
+                $tagsAssigned[] = $tagId;
156
+            } catch (Exception $e) {
157
+                if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
158
+                    throw $e;
159
+                }
160
+                // ignore existing relations
161
+            }
162
+        }
163
+
164
+        $this->updateEtagForTags($tagIds);
165
+
166
+        $this->connection->commit();
167
+        if (empty($tagsAssigned)) {
168
+            return;
169
+        }
170
+
171
+        $this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent(
172
+            MapperEvent::EVENT_ASSIGN,
173
+            $objectType,
174
+            $objId,
175
+            $tagsAssigned
176
+        ));
177
+        $this->dispatcher->dispatchTyped(new TagAssignedEvent($objectType, [$objId], $tagsAssigned));
178
+    }
179
+
180
+    /**
181
+     * {@inheritdoc}
182
+     */
183
+    public function unassignTags(string $objId, string $objectType, $tagIds): void {
184
+        if (!\is_array($tagIds)) {
185
+            $tagIds = [$tagIds];
186
+        }
187
+
188
+        $this->assertTagsExist($tagIds);
189
+
190
+        $query = $this->connection->getQueryBuilder();
191
+        $query->delete(self::RELATION_TABLE)
192
+            ->where($query->expr()->eq('objectid', $query->createParameter('objectid')))
193
+            ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
194
+            ->andWhere($query->expr()->in('systemtagid', $query->createParameter('tagids')))
195
+            ->setParameter('objectid', $objId)
196
+            ->setParameter('objecttype', $objectType)
197
+            ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY)
198
+            ->executeStatement();
199
+
200
+        $this->updateEtagForTags($tagIds);
201
+
202
+        $this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
203
+            MapperEvent::EVENT_UNASSIGN,
204
+            $objectType,
205
+            $objId,
206
+            $tagIds
207
+        ));
208
+        $this->dispatcher->dispatchTyped(new TagUnassignedEvent($objectType, [$objId], $tagIds));
209
+    }
210
+
211
+    /**
212
+     * Update the etag for the given tags.
213
+     *
214
+     * @param string[] $tagIds
215
+     */
216
+    private function updateEtagForTags(array $tagIds): void {
217
+        // Update etag after assigning tags
218
+        $md5 = md5(json_encode(time()));
219
+        $query = $this->connection->getQueryBuilder();
220
+        $query->update('systemtag')
221
+            ->set('etag', $query->createNamedParameter($md5))
222
+            ->where($query->expr()->in('id', $query->createNamedParameter($tagIds, IQueryBuilder::PARAM_INT_ARRAY)));
223
+        $query->executeStatement();
224
+    }
225
+
226
+    /**
227
+     * {@inheritdoc}
228
+     */
229
+    public function haveTag($objIds, string $objectType, string $tagId, bool $all = true): bool {
230
+        $this->assertTagsExist([$tagId]);
231
+
232
+        if (!\is_array($objIds)) {
233
+            $objIds = [$objIds];
234
+        }
235
+
236
+        $query = $this->connection->getQueryBuilder();
237
+
238
+        if (!$all) {
239
+            // If we only need one entry, we make the query lighter, by not
240
+            // counting the elements
241
+            $query->select('*')
242
+                ->setMaxResults(1);
243
+        } else {
244
+            $query->select($query->func()->count($query->expr()->literal(1)));
245
+        }
246
+
247
+        $query->from(self::RELATION_TABLE)
248
+            ->where($query->expr()->in('objectid', $query->createParameter('objectids')))
249
+            ->andWhere($query->expr()->eq('objecttype', $query->createParameter('objecttype')))
250
+            ->andWhere($query->expr()->eq('systemtagid', $query->createParameter('tagid')))
251
+            ->setParameter('objectids', $objIds, IQueryBuilder::PARAM_STR_ARRAY)
252
+            ->setParameter('tagid', $tagId)
253
+            ->setParameter('objecttype', $objectType);
254
+
255
+        $result = $query->executeQuery();
256
+        $row = $result->fetch(\PDO::FETCH_NUM);
257
+        $result->closeCursor();
258
+
259
+        if ($all) {
260
+            return ((int)$row[0] === \count($objIds));
261
+        }
262
+
263
+        return (bool)$row;
264
+    }
265
+
266
+    /**
267
+     * Asserts that all the given tag ids exist.
268
+     *
269
+     * @param string[] $tagIds tag ids to check
270
+     *
271
+     * @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist
272
+     */
273
+    private function assertTagsExist(array $tagIds): void {
274
+        $tags = $this->tagManager->getTagsByIds($tagIds);
275
+        if (\count($tags) !== \count($tagIds)) {
276
+            // at least one tag missing, bail out
277
+            $foundTagIds = array_map(
278
+                function (ISystemTag $tag) {
279
+                    return $tag->getId();
280
+                },
281
+                $tags
282
+            );
283
+            $missingTagIds = array_diff($tagIds, $foundTagIds);
284
+            throw new TagNotFoundException(
285
+                'Tags not found', 0, null, $missingTagIds
286
+            );
287
+        }
288
+    }
289
+
290
+    /**
291
+     * {@inheritdoc}
292
+     */
293
+    public function setObjectIdsForTag(string $tagId, string $objectType, array $objectIds): void {
294
+        $currentObjectIds = $this->getObjectIdsForTags($tagId, $objectType);
295
+        $removedObjectIds = array_diff($currentObjectIds, $objectIds);
296
+        $addedObjectIds = array_diff($objectIds, $currentObjectIds);
297
+
298
+        $this->connection->beginTransaction();
299
+        $query = $this->connection->getQueryBuilder();
300
+        $query->delete(self::RELATION_TABLE)
301
+            ->where($query->expr()->eq('systemtagid', $query->createNamedParameter($tagId, IQueryBuilder::PARAM_INT)))
302
+            ->andWhere($query->expr()->eq('objecttype', $query->createNamedParameter($objectType)))
303
+            ->executeStatement();
304
+        $this->connection->commit();
305
+
306
+        foreach ($removedObjectIds as $objectId) {
307
+            $this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
308
+                MapperEvent::EVENT_UNASSIGN,
309
+                $objectType,
310
+                (string)$objectId,
311
+                [(int)$tagId]
312
+            ));
313
+        }
314
+        if (!empty($removedObjectIds)) {
315
+            $this->dispatcher->dispatchTyped(new TagUnassignedEvent($objectType, array_map(fn ($objectId) => (string)$objectId, $removedObjectIds), [(int)$tagId]));
316
+        }
317
+
318
+        if (empty($objectIds)) {
319
+            return;
320
+        }
321
+
322
+        $this->connection->beginTransaction();
323
+        $query = $this->connection->getQueryBuilder();
324
+        $query->insert(self::RELATION_TABLE)
325
+            ->values([
326
+                'systemtagid' => $query->createNamedParameter($tagId, IQueryBuilder::PARAM_INT),
327
+                'objecttype' => $query->createNamedParameter($objectType),
328
+                'objectid' => $query->createParameter('objectid'),
329
+            ]);
330
+
331
+        foreach (array_unique($objectIds) as $objectId) {
332
+            $query->setParameter('objectid', (string)$objectId);
333
+            $query->executeStatement();
334
+        }
335
+
336
+        $this->updateEtagForTags([$tagId]);
337
+        $this->connection->commit();
338
+
339
+        // Dispatch assign events for new object ids
340
+        foreach ($addedObjectIds as $objectId) {
341
+            $this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent(
342
+                MapperEvent::EVENT_ASSIGN,
343
+                $objectType,
344
+                (string)$objectId,
345
+                [(int)$tagId]
346
+            ));
347
+        }
348
+        if (!empty($addedObjectIds)) {
349
+            $this->dispatcher->dispatchTyped(new TagAssignedEvent($objectType, array_map(fn ($objectId) => (string)$objectId, $addedObjectIds), [(int)$tagId]));
350
+        }
351
+
352
+        // Dispatch unassign events for removed object ids
353
+        foreach ($removedObjectIds as $objectId) {
354
+            $this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent(
355
+                MapperEvent::EVENT_UNASSIGN,
356
+                $objectType,
357
+                (string)$objectId,
358
+                [(int)$tagId]
359
+            ));
360
+        }
361
+    }
362
+
363
+    /**
364
+     * {@inheritdoc}
365
+     */
366
+    public function getAvailableObjectTypes(): array {
367
+        $query = $this->connection->getQueryBuilder();
368
+        $query->selectDistinct('objecttype')
369
+            ->from(self::RELATION_TABLE);
370
+
371
+        $result = $query->executeQuery();
372
+        $objectTypes = [];
373
+        while ($row = $result->fetch()) {
374
+            $objectTypes[] = $row['objecttype'];
375
+        }
376
+        $result->closeCursor();
377
+
378
+        return $objectTypes;
379
+    }
380 380
 }
Please login to merge, or discard this patch.
tests/lib/SystemTag/SystemTagObjectMapperTest.php 1 patch
Indentation   +406 added lines, -406 removed lines patch added patch discarded remove patch
@@ -30,410 +30,410 @@
 block discarded – undo
30 30
  * @package Test\SystemTag
31 31
  */
32 32
 class SystemTagObjectMapperTest extends TestCase {
33
-	/**
34
-	 * @var ISystemTagManager
35
-	 **/
36
-	private $tagManager;
37
-
38
-	/**
39
-	 * @var ISystemTagObjectMapper
40
-	 **/
41
-	private $tagMapper;
42
-
43
-	/**
44
-	 * @var IDBConnection
45
-	 */
46
-	private $connection;
47
-
48
-	/**
49
-	 * @var IEventDispatcher
50
-	 */
51
-	private $dispatcher;
52
-
53
-	/**
54
-	 * @var ISystemTag
55
-	 */
56
-	private $tag1;
57
-
58
-	/**
59
-	 * @var ISystemTag
60
-	 */
61
-	private $tag2;
62
-
63
-	/**
64
-	 * @var ISystemTag
65
-	 */
66
-	private $tag3;
67
-
68
-	protected function setUp(): void {
69
-		parent::setUp();
70
-
71
-		$this->connection = Server::get(IDBConnection::class);
72
-		$this->pruneTagsTables();
73
-
74
-		$this->tagManager = $this->createMock(ISystemTagManager::class);
75
-		$this->dispatcher = $this->createMock(IEventDispatcher::class);
76
-
77
-		$this->tagMapper = new SystemTagObjectMapper(
78
-			$this->connection,
79
-			$this->tagManager,
80
-			$this->dispatcher
81
-		);
82
-
83
-		$this->tag1 = new SystemTag(1, 'testtag1', false, false);
84
-		$this->tag2 = new SystemTag(2, 'testtag2', true, false);
85
-		$this->tag3 = new SystemTag(3, 'testtag3', false, false);
86
-
87
-		$this->tagManager->expects($this->any())
88
-			->method('getTagsByIds')
89
-			->willReturnCallback(function ($tagIds) {
90
-				$result = [];
91
-				if (in_array(1, $tagIds)) {
92
-					$result[1] = $this->tag1;
93
-				}
94
-				if (in_array(2, $tagIds)) {
95
-					$result[2] = $this->tag2;
96
-				}
97
-				if (in_array(3, $tagIds)) {
98
-					$result[3] = $this->tag3;
99
-				}
100
-				return $result;
101
-			});
102
-
103
-		$this->tagMapper->assignTags('1', 'testtype', $this->tag1->getId());
104
-		$this->tagMapper->assignTags('1', 'testtype', $this->tag2->getId());
105
-		$this->tagMapper->assignTags('2', 'testtype', $this->tag1->getId());
106
-		$this->tagMapper->assignTags('3', 'anothertype', $this->tag1->getId());
107
-	}
108
-
109
-	protected function tearDown(): void {
110
-		$this->pruneTagsTables();
111
-		parent::tearDown();
112
-	}
113
-
114
-	protected function pruneTagsTables() {
115
-		$query = $this->connection->getQueryBuilder();
116
-		$query->delete(SystemTagObjectMapper::RELATION_TABLE)->executeStatement();
117
-		$query->delete(SystemTagManager::TAG_TABLE)->executeStatement();
118
-	}
119
-
120
-	public function testGetTagIdsForObjects(): void {
121
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
122
-			['1', '2', '3', '4'],
123
-			'testtype'
124
-		);
125
-
126
-		$this->assertEquals([
127
-			'1' => [$this->tag1->getId(), $this->tag2->getId()],
128
-			'2' => [$this->tag1->getId()],
129
-			'3' => [],
130
-			'4' => [],
131
-		], $tagIdMapping);
132
-	}
133
-
134
-	public function testGetTagIdsForNoObjects(): void {
135
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
136
-			[],
137
-			'testtype'
138
-		);
139
-
140
-		$this->assertEquals([], $tagIdMapping);
141
-	}
142
-
143
-	public function testGetTagIdsForALotOfObjects(): void {
144
-		$ids = range(1, 10500);
145
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
146
-			$ids,
147
-			'testtype'
148
-		);
149
-
150
-		$this->assertEquals(10500, count($tagIdMapping));
151
-		$this->assertEquals([$this->tag1->getId(), $this->tag2->getId()], $tagIdMapping[1]);
152
-	}
153
-
154
-	public function testGetObjectsForTags(): void {
155
-		$objectIds = $this->tagMapper->getObjectIdsForTags(
156
-			[$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
157
-			'testtype'
158
-		);
159
-		sort($objectIds);
160
-
161
-		$this->assertEquals([
162
-			'1',
163
-			'2',
164
-		], $objectIds);
165
-	}
166
-
167
-	public function testGetObjectsForTagsLimit(): void {
168
-		$objectIds = $this->tagMapper->getObjectIdsForTags(
169
-			[$this->tag1->getId()],
170
-			'testtype',
171
-			1
172
-		);
173
-
174
-		$this->assertEquals([
175
-			1,
176
-		], $objectIds);
177
-	}
178
-
179
-
180
-	public function testGetObjectsForTagsLimitWithMultipleTags(): void {
181
-		$this->expectException(\InvalidArgumentException::class);
182
-
183
-		$this->tagMapper->getObjectIdsForTags(
184
-			[$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
185
-			'testtype',
186
-			1
187
-		);
188
-	}
189
-
190
-	public function testGetObjectsForTagsLimitOffset(): void {
191
-		$objectIds = $this->tagMapper->getObjectIdsForTags(
192
-			[$this->tag1->getId()],
193
-			'testtype',
194
-			1,
195
-			'1'
196
-		);
197
-
198
-		$this->assertEquals([
199
-			2,
200
-		], $objectIds);
201
-	}
202
-
203
-
204
-	public function testGetObjectsForNonExistingTag(): void {
205
-		$this->expectException(TagNotFoundException::class);
206
-
207
-		$this->tagMapper->getObjectIdsForTags(
208
-			[100],
209
-			'testtype'
210
-		);
211
-	}
212
-
213
-	public function testAssignUnassignTags(): void {
214
-		$event = null;
215
-		$this->dispatcher->expects($this->any())->method('dispatchTyped')->willReturnCallback(function (Event $e) use (&$event) {
216
-			$event = $e;
217
-		});
218
-
219
-		$this->tagMapper->unassignTags('1', 'testtype', [$this->tag1->getId()]);
220
-
221
-		$this->assertNotNull($event);
222
-		$this->assertEquals(TagUnassignedEvent::class, $event::class);
223
-		$this->assertEquals('testtype', $event->getObjectType());
224
-		$this->assertCount(1, $event->getObjectIds());
225
-		$this->assertEquals('1', current($event->getObjectIds()));
226
-		$this->assertCount(1, $event->getTags());
227
-		$this->assertEquals($this->tag1->getId(), current($event->getTags()));
228
-
229
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects('1', 'testtype');
230
-		$this->assertEquals([
231
-			1 => [$this->tag2->getId()],
232
-		], $tagIdMapping);
233
-
234
-		$this->tagMapper->assignTags('1', 'testtype', [$this->tag1->getId()]);
235
-
236
-		$this->assertNotNull($event);
237
-		$this->assertEquals(TagAssignedEvent::class, $event::class);
238
-		$this->assertEquals('testtype', $event->getObjectType());
239
-		$this->assertCount(1, $event->getObjectIds());
240
-		$this->assertEquals('1', current($event->getObjectIds()));
241
-		$this->assertCount(1, $event->getTags());
242
-		$this->assertEquals($this->tag1->getId(), current($event->getTags()));
243
-
244
-		$this->tagMapper->assignTags('1', 'testtype', $this->tag3->getId());
245
-
246
-		$this->assertNotNull($event);
247
-		$this->assertEquals(TagAssignedEvent::class, $event::class);
248
-		$this->assertEquals('testtype', $event->getObjectType());
249
-		$this->assertCount(1, $event->getObjectIds());
250
-		$this->assertEquals('1', current($event->getObjectIds()));
251
-		$this->assertCount(1, $event->getTags());
252
-		$this->assertEquals($this->tag3->getId(), current($event->getTags()));
253
-
254
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects('1', 'testtype');
255
-
256
-		$this->assertEquals([
257
-			'1' => [$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
258
-		], $tagIdMapping);
259
-	}
260
-
261
-	public function testReAssignUnassignTags(): void {
262
-		// reassign tag1
263
-		$this->tagMapper->assignTags('1', 'testtype', [$this->tag1->getId()]);
264
-
265
-		// tag 3 was never assigned
266
-		$this->tagMapper->unassignTags('1', 'testtype', [$this->tag3->getId()]);
267
-
268
-		$this->assertTrue(true, 'No error when reassigning/unassigning');
269
-	}
270
-
271
-
272
-	public function testAssignNonExistingTags(): void {
273
-		$this->expectException(TagNotFoundException::class);
274
-
275
-		$this->tagMapper->assignTags('1', 'testtype', [100]);
276
-	}
277
-
278
-	public function testAssignNonExistingTagInArray(): void {
279
-		$caught = false;
280
-		try {
281
-			$this->tagMapper->assignTags('1', 'testtype', [100, $this->tag3->getId()]);
282
-		} catch (TagNotFoundException $e) {
283
-			$caught = true;
284
-		}
285
-
286
-		$this->assertTrue($caught, 'Exception thrown');
287
-
288
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
289
-			['1'],
290
-			'testtype'
291
-		);
292
-
293
-		$this->assertEquals([
294
-			'1' => [$this->tag1->getId(), $this->tag2->getId()],
295
-		], $tagIdMapping, 'None of the tags got assigned');
296
-	}
297
-
298
-
299
-	public function testUnassignNonExistingTags(): void {
300
-		$this->expectException(TagNotFoundException::class);
301
-
302
-		$this->tagMapper->unassignTags('1', 'testtype', [100]);
303
-	}
304
-
305
-	public function testUnassignNonExistingTagsInArray(): void {
306
-		$caught = false;
307
-		try {
308
-			$this->tagMapper->unassignTags('1', 'testtype', [100, $this->tag1->getId()]);
309
-		} catch (TagNotFoundException $e) {
310
-			$caught = true;
311
-		}
312
-
313
-		$this->assertTrue($caught, 'Exception thrown');
314
-
315
-		$tagIdMapping = $this->tagMapper->getTagIdsForObjects(
316
-			[1],
317
-			'testtype'
318
-		);
319
-
320
-		$this->assertEquals([
321
-			'1' => [$this->tag1->getId(), $this->tag2->getId()],
322
-		], $tagIdMapping, 'None of the tags got unassigned');
323
-	}
324
-
325
-	public function testHaveTagAllMatches(): void {
326
-		$this->assertTrue(
327
-			$this->tagMapper->haveTag(
328
-				['1'],
329
-				'testtype',
330
-				$this->tag1->getId(),
331
-				true
332
-			),
333
-			'object 1 has the tag tag1'
334
-		);
335
-
336
-		$this->assertTrue(
337
-			$this->tagMapper->haveTag(
338
-				['1', '2'],
339
-				'testtype',
340
-				$this->tag1->getId(),
341
-				true
342
-			),
343
-			'object 1 and object 2 ALL have the tag tag1'
344
-		);
345
-
346
-		$this->assertFalse(
347
-			$this->tagMapper->haveTag(
348
-				['1', '2'],
349
-				'testtype',
350
-				$this->tag2->getId(),
351
-				true
352
-			),
353
-			'object 1 has tag2 but not object 2, so not ALL of them'
354
-		);
355
-
356
-		$this->assertFalse(
357
-			$this->tagMapper->haveTag(
358
-				['2'],
359
-				'testtype',
360
-				$this->tag2->getId(),
361
-				true
362
-			),
363
-			'object 2 does not have tag2'
364
-		);
365
-
366
-		$this->assertFalse(
367
-			$this->tagMapper->haveTag(
368
-				['3'],
369
-				'testtype',
370
-				$this->tag2->getId(),
371
-				true
372
-			),
373
-			'object 3 does not have tag1 due to different type'
374
-		);
375
-	}
376
-
377
-	public function testHaveTagAtLeastOneMatch(): void {
378
-		$this->assertTrue(
379
-			$this->tagMapper->haveTag(
380
-				['1'],
381
-				'testtype',
382
-				$this->tag1->getId(),
383
-				false
384
-			),
385
-			'object1 has the tag tag1'
386
-		);
387
-
388
-		$this->assertTrue(
389
-			$this->tagMapper->haveTag(
390
-				['1', '2'],
391
-				'testtype',
392
-				$this->tag1->getId(),
393
-				false
394
-			),
395
-			'object 1  and object 2 both the tag tag1'
396
-		);
397
-
398
-		$this->assertTrue(
399
-			$this->tagMapper->haveTag(
400
-				['1', '2'],
401
-				'testtype',
402
-				$this->tag2->getId(),
403
-				false
404
-			),
405
-			'at least object 1 has the tag tag2'
406
-		);
407
-
408
-		$this->assertFalse(
409
-			$this->tagMapper->haveTag(
410
-				['2'],
411
-				'testtype',
412
-				$this->tag2->getId(),
413
-				false
414
-			),
415
-			'object 2 does not have tag2'
416
-		);
417
-
418
-		$this->assertFalse(
419
-			$this->tagMapper->haveTag(
420
-				['3'],
421
-				'testtype',
422
-				$this->tag2->getId(),
423
-				false
424
-			),
425
-			'object 3 does not have tag1 due to different type'
426
-		);
427
-	}
428
-
429
-
430
-	public function testHaveTagNonExisting(): void {
431
-		$this->expectException(TagNotFoundException::class);
432
-
433
-		$this->tagMapper->haveTag(
434
-			['1'],
435
-			'testtype',
436
-			100
437
-		);
438
-	}
33
+    /**
34
+     * @var ISystemTagManager
35
+     **/
36
+    private $tagManager;
37
+
38
+    /**
39
+     * @var ISystemTagObjectMapper
40
+     **/
41
+    private $tagMapper;
42
+
43
+    /**
44
+     * @var IDBConnection
45
+     */
46
+    private $connection;
47
+
48
+    /**
49
+     * @var IEventDispatcher
50
+     */
51
+    private $dispatcher;
52
+
53
+    /**
54
+     * @var ISystemTag
55
+     */
56
+    private $tag1;
57
+
58
+    /**
59
+     * @var ISystemTag
60
+     */
61
+    private $tag2;
62
+
63
+    /**
64
+     * @var ISystemTag
65
+     */
66
+    private $tag3;
67
+
68
+    protected function setUp(): void {
69
+        parent::setUp();
70
+
71
+        $this->connection = Server::get(IDBConnection::class);
72
+        $this->pruneTagsTables();
73
+
74
+        $this->tagManager = $this->createMock(ISystemTagManager::class);
75
+        $this->dispatcher = $this->createMock(IEventDispatcher::class);
76
+
77
+        $this->tagMapper = new SystemTagObjectMapper(
78
+            $this->connection,
79
+            $this->tagManager,
80
+            $this->dispatcher
81
+        );
82
+
83
+        $this->tag1 = new SystemTag(1, 'testtag1', false, false);
84
+        $this->tag2 = new SystemTag(2, 'testtag2', true, false);
85
+        $this->tag3 = new SystemTag(3, 'testtag3', false, false);
86
+
87
+        $this->tagManager->expects($this->any())
88
+            ->method('getTagsByIds')
89
+            ->willReturnCallback(function ($tagIds) {
90
+                $result = [];
91
+                if (in_array(1, $tagIds)) {
92
+                    $result[1] = $this->tag1;
93
+                }
94
+                if (in_array(2, $tagIds)) {
95
+                    $result[2] = $this->tag2;
96
+                }
97
+                if (in_array(3, $tagIds)) {
98
+                    $result[3] = $this->tag3;
99
+                }
100
+                return $result;
101
+            });
102
+
103
+        $this->tagMapper->assignTags('1', 'testtype', $this->tag1->getId());
104
+        $this->tagMapper->assignTags('1', 'testtype', $this->tag2->getId());
105
+        $this->tagMapper->assignTags('2', 'testtype', $this->tag1->getId());
106
+        $this->tagMapper->assignTags('3', 'anothertype', $this->tag1->getId());
107
+    }
108
+
109
+    protected function tearDown(): void {
110
+        $this->pruneTagsTables();
111
+        parent::tearDown();
112
+    }
113
+
114
+    protected function pruneTagsTables() {
115
+        $query = $this->connection->getQueryBuilder();
116
+        $query->delete(SystemTagObjectMapper::RELATION_TABLE)->executeStatement();
117
+        $query->delete(SystemTagManager::TAG_TABLE)->executeStatement();
118
+    }
119
+
120
+    public function testGetTagIdsForObjects(): void {
121
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects(
122
+            ['1', '2', '3', '4'],
123
+            'testtype'
124
+        );
125
+
126
+        $this->assertEquals([
127
+            '1' => [$this->tag1->getId(), $this->tag2->getId()],
128
+            '2' => [$this->tag1->getId()],
129
+            '3' => [],
130
+            '4' => [],
131
+        ], $tagIdMapping);
132
+    }
133
+
134
+    public function testGetTagIdsForNoObjects(): void {
135
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects(
136
+            [],
137
+            'testtype'
138
+        );
139
+
140
+        $this->assertEquals([], $tagIdMapping);
141
+    }
142
+
143
+    public function testGetTagIdsForALotOfObjects(): void {
144
+        $ids = range(1, 10500);
145
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects(
146
+            $ids,
147
+            'testtype'
148
+        );
149
+
150
+        $this->assertEquals(10500, count($tagIdMapping));
151
+        $this->assertEquals([$this->tag1->getId(), $this->tag2->getId()], $tagIdMapping[1]);
152
+    }
153
+
154
+    public function testGetObjectsForTags(): void {
155
+        $objectIds = $this->tagMapper->getObjectIdsForTags(
156
+            [$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
157
+            'testtype'
158
+        );
159
+        sort($objectIds);
160
+
161
+        $this->assertEquals([
162
+            '1',
163
+            '2',
164
+        ], $objectIds);
165
+    }
166
+
167
+    public function testGetObjectsForTagsLimit(): void {
168
+        $objectIds = $this->tagMapper->getObjectIdsForTags(
169
+            [$this->tag1->getId()],
170
+            'testtype',
171
+            1
172
+        );
173
+
174
+        $this->assertEquals([
175
+            1,
176
+        ], $objectIds);
177
+    }
178
+
179
+
180
+    public function testGetObjectsForTagsLimitWithMultipleTags(): void {
181
+        $this->expectException(\InvalidArgumentException::class);
182
+
183
+        $this->tagMapper->getObjectIdsForTags(
184
+            [$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
185
+            'testtype',
186
+            1
187
+        );
188
+    }
189
+
190
+    public function testGetObjectsForTagsLimitOffset(): void {
191
+        $objectIds = $this->tagMapper->getObjectIdsForTags(
192
+            [$this->tag1->getId()],
193
+            'testtype',
194
+            1,
195
+            '1'
196
+        );
197
+
198
+        $this->assertEquals([
199
+            2,
200
+        ], $objectIds);
201
+    }
202
+
203
+
204
+    public function testGetObjectsForNonExistingTag(): void {
205
+        $this->expectException(TagNotFoundException::class);
206
+
207
+        $this->tagMapper->getObjectIdsForTags(
208
+            [100],
209
+            'testtype'
210
+        );
211
+    }
212
+
213
+    public function testAssignUnassignTags(): void {
214
+        $event = null;
215
+        $this->dispatcher->expects($this->any())->method('dispatchTyped')->willReturnCallback(function (Event $e) use (&$event) {
216
+            $event = $e;
217
+        });
218
+
219
+        $this->tagMapper->unassignTags('1', 'testtype', [$this->tag1->getId()]);
220
+
221
+        $this->assertNotNull($event);
222
+        $this->assertEquals(TagUnassignedEvent::class, $event::class);
223
+        $this->assertEquals('testtype', $event->getObjectType());
224
+        $this->assertCount(1, $event->getObjectIds());
225
+        $this->assertEquals('1', current($event->getObjectIds()));
226
+        $this->assertCount(1, $event->getTags());
227
+        $this->assertEquals($this->tag1->getId(), current($event->getTags()));
228
+
229
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects('1', 'testtype');
230
+        $this->assertEquals([
231
+            1 => [$this->tag2->getId()],
232
+        ], $tagIdMapping);
233
+
234
+        $this->tagMapper->assignTags('1', 'testtype', [$this->tag1->getId()]);
235
+
236
+        $this->assertNotNull($event);
237
+        $this->assertEquals(TagAssignedEvent::class, $event::class);
238
+        $this->assertEquals('testtype', $event->getObjectType());
239
+        $this->assertCount(1, $event->getObjectIds());
240
+        $this->assertEquals('1', current($event->getObjectIds()));
241
+        $this->assertCount(1, $event->getTags());
242
+        $this->assertEquals($this->tag1->getId(), current($event->getTags()));
243
+
244
+        $this->tagMapper->assignTags('1', 'testtype', $this->tag3->getId());
245
+
246
+        $this->assertNotNull($event);
247
+        $this->assertEquals(TagAssignedEvent::class, $event::class);
248
+        $this->assertEquals('testtype', $event->getObjectType());
249
+        $this->assertCount(1, $event->getObjectIds());
250
+        $this->assertEquals('1', current($event->getObjectIds()));
251
+        $this->assertCount(1, $event->getTags());
252
+        $this->assertEquals($this->tag3->getId(), current($event->getTags()));
253
+
254
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects('1', 'testtype');
255
+
256
+        $this->assertEquals([
257
+            '1' => [$this->tag1->getId(), $this->tag2->getId(), $this->tag3->getId()],
258
+        ], $tagIdMapping);
259
+    }
260
+
261
+    public function testReAssignUnassignTags(): void {
262
+        // reassign tag1
263
+        $this->tagMapper->assignTags('1', 'testtype', [$this->tag1->getId()]);
264
+
265
+        // tag 3 was never assigned
266
+        $this->tagMapper->unassignTags('1', 'testtype', [$this->tag3->getId()]);
267
+
268
+        $this->assertTrue(true, 'No error when reassigning/unassigning');
269
+    }
270
+
271
+
272
+    public function testAssignNonExistingTags(): void {
273
+        $this->expectException(TagNotFoundException::class);
274
+
275
+        $this->tagMapper->assignTags('1', 'testtype', [100]);
276
+    }
277
+
278
+    public function testAssignNonExistingTagInArray(): void {
279
+        $caught = false;
280
+        try {
281
+            $this->tagMapper->assignTags('1', 'testtype', [100, $this->tag3->getId()]);
282
+        } catch (TagNotFoundException $e) {
283
+            $caught = true;
284
+        }
285
+
286
+        $this->assertTrue($caught, 'Exception thrown');
287
+
288
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects(
289
+            ['1'],
290
+            'testtype'
291
+        );
292
+
293
+        $this->assertEquals([
294
+            '1' => [$this->tag1->getId(), $this->tag2->getId()],
295
+        ], $tagIdMapping, 'None of the tags got assigned');
296
+    }
297
+
298
+
299
+    public function testUnassignNonExistingTags(): void {
300
+        $this->expectException(TagNotFoundException::class);
301
+
302
+        $this->tagMapper->unassignTags('1', 'testtype', [100]);
303
+    }
304
+
305
+    public function testUnassignNonExistingTagsInArray(): void {
306
+        $caught = false;
307
+        try {
308
+            $this->tagMapper->unassignTags('1', 'testtype', [100, $this->tag1->getId()]);
309
+        } catch (TagNotFoundException $e) {
310
+            $caught = true;
311
+        }
312
+
313
+        $this->assertTrue($caught, 'Exception thrown');
314
+
315
+        $tagIdMapping = $this->tagMapper->getTagIdsForObjects(
316
+            [1],
317
+            'testtype'
318
+        );
319
+
320
+        $this->assertEquals([
321
+            '1' => [$this->tag1->getId(), $this->tag2->getId()],
322
+        ], $tagIdMapping, 'None of the tags got unassigned');
323
+    }
324
+
325
+    public function testHaveTagAllMatches(): void {
326
+        $this->assertTrue(
327
+            $this->tagMapper->haveTag(
328
+                ['1'],
329
+                'testtype',
330
+                $this->tag1->getId(),
331
+                true
332
+            ),
333
+            'object 1 has the tag tag1'
334
+        );
335
+
336
+        $this->assertTrue(
337
+            $this->tagMapper->haveTag(
338
+                ['1', '2'],
339
+                'testtype',
340
+                $this->tag1->getId(),
341
+                true
342
+            ),
343
+            'object 1 and object 2 ALL have the tag tag1'
344
+        );
345
+
346
+        $this->assertFalse(
347
+            $this->tagMapper->haveTag(
348
+                ['1', '2'],
349
+                'testtype',
350
+                $this->tag2->getId(),
351
+                true
352
+            ),
353
+            'object 1 has tag2 but not object 2, so not ALL of them'
354
+        );
355
+
356
+        $this->assertFalse(
357
+            $this->tagMapper->haveTag(
358
+                ['2'],
359
+                'testtype',
360
+                $this->tag2->getId(),
361
+                true
362
+            ),
363
+            'object 2 does not have tag2'
364
+        );
365
+
366
+        $this->assertFalse(
367
+            $this->tagMapper->haveTag(
368
+                ['3'],
369
+                'testtype',
370
+                $this->tag2->getId(),
371
+                true
372
+            ),
373
+            'object 3 does not have tag1 due to different type'
374
+        );
375
+    }
376
+
377
+    public function testHaveTagAtLeastOneMatch(): void {
378
+        $this->assertTrue(
379
+            $this->tagMapper->haveTag(
380
+                ['1'],
381
+                'testtype',
382
+                $this->tag1->getId(),
383
+                false
384
+            ),
385
+            'object1 has the tag tag1'
386
+        );
387
+
388
+        $this->assertTrue(
389
+            $this->tagMapper->haveTag(
390
+                ['1', '2'],
391
+                'testtype',
392
+                $this->tag1->getId(),
393
+                false
394
+            ),
395
+            'object 1  and object 2 both the tag tag1'
396
+        );
397
+
398
+        $this->assertTrue(
399
+            $this->tagMapper->haveTag(
400
+                ['1', '2'],
401
+                'testtype',
402
+                $this->tag2->getId(),
403
+                false
404
+            ),
405
+            'at least object 1 has the tag tag2'
406
+        );
407
+
408
+        $this->assertFalse(
409
+            $this->tagMapper->haveTag(
410
+                ['2'],
411
+                'testtype',
412
+                $this->tag2->getId(),
413
+                false
414
+            ),
415
+            'object 2 does not have tag2'
416
+        );
417
+
418
+        $this->assertFalse(
419
+            $this->tagMapper->haveTag(
420
+                ['3'],
421
+                'testtype',
422
+                $this->tag2->getId(),
423
+                false
424
+            ),
425
+            'object 3 does not have tag1 due to different type'
426
+        );
427
+    }
428
+
429
+
430
+    public function testHaveTagNonExisting(): void {
431
+        $this->expectException(TagNotFoundException::class);
432
+
433
+        $this->tagMapper->haveTag(
434
+            ['1'],
435
+            'testtype',
436
+            100
437
+        );
438
+    }
439 439
 }
Please login to merge, or discard this patch.