Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
8.75% |
7 / 80 |
|
12.00% |
3 / 25 |
CRAP | |
0.00% |
0 / 1 |
TagsService | |
8.75% |
7 / 80 |
|
12.00% |
3 / 25 |
1077.16 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parse | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
linkTagsToRecord | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getOrCreateTagByText | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
linkTagToResource | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
unlinkTagFromResource | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
unlinkTagsFromRecord | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
fixDuplicateTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasCaseSensitiveTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStatistics | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getNonListTagsFuzzilyMatchingString | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTagsByText | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTagByText | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResourcesMatchingTagQuery | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getTagBrowseList | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRecordTags | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRecordTagsFromFavorites | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getRecordTagsNotInFavorites | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getDuplicateTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUserTagsFromFavorites | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getListTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUniqueTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getResourceTagsPaginator | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getUserListsByTagAndId | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
deleteOrphanedTags | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Service for handling tag processing. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010-2024. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Tags |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org/wiki/ Wiki |
28 | */ |
29 | |
30 | namespace VuFind\Tags; |
31 | |
32 | use Laminas\Paginator\Paginator; |
33 | use VuFind\Db\Entity\ResourceEntityInterface; |
34 | use VuFind\Db\Entity\TagsEntityInterface; |
35 | use VuFind\Db\Entity\UserEntityInterface; |
36 | use VuFind\Db\Entity\UserListEntityInterface; |
37 | use VuFind\Db\Service\Feature\TransactionInterface; |
38 | use VuFind\Db\Service\ResourceTagsServiceInterface; |
39 | use VuFind\Db\Service\TagServiceInterface; |
40 | use VuFind\Db\Service\UserListServiceInterface; |
41 | use VuFind\Db\Table\DbTableAwareInterface; |
42 | use VuFind\Db\Table\DbTableAwareTrait; |
43 | use VuFind\Record\ResourcePopulator; |
44 | use VuFind\RecordDriver\AbstractBase as RecordDriver; |
45 | |
46 | use function is_array; |
47 | |
48 | /** |
49 | * Service for handling tag processing. |
50 | * |
51 | * @category VuFind |
52 | * @package Tags |
53 | * @author Demian Katz <demian.katz@villanova.edu> |
54 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
55 | * @link https://vufind.org/wiki/ Wiki |
56 | */ |
57 | class TagsService implements DbTableAwareInterface |
58 | { |
59 | use DbTableAwareTrait; |
60 | |
61 | /** |
62 | * Constructor |
63 | * |
64 | * @param TagServiceInterface $tagDbService Tag database service |
65 | * @param ResourceTagsServiceInterface&TransactionInterface $resourceTagsService Resource/Tags database service |
66 | * @param UserListServiceInterface $userListService User list database service |
67 | * @param ResourcePopulator $resourcePopulator Resource populator service |
68 | * @param int $maxLength Maximum tag length |
69 | * @param bool $caseSensitive Are tags case sensitive? |
70 | */ |
71 | public function __construct( |
72 | protected TagServiceInterface $tagDbService, |
73 | protected ResourceTagsServiceInterface&TransactionInterface $resourceTagsService, |
74 | protected UserListServiceInterface $userListService, |
75 | protected ResourcePopulator $resourcePopulator, |
76 | protected int $maxLength = 64, |
77 | protected bool $caseSensitive = false |
78 | ) { |
79 | } |
80 | |
81 | /** |
82 | * Parse a user-submitted tag string into an array of separate tags. |
83 | * |
84 | * @param string $tags User-provided tags |
85 | * |
86 | * @return array |
87 | */ |
88 | public function parse($tags) |
89 | { |
90 | preg_match_all('/"[^"]*"|[^ ]+/', trim($tags), $words); |
91 | $result = []; |
92 | foreach ($words[0] as $tag) { |
93 | // Wipe out double-quotes and trim over-long tags: |
94 | $result[] = substr(str_replace('"', '', $tag), 0, $this->maxLength); |
95 | } |
96 | return array_unique($result); |
97 | } |
98 | |
99 | /** |
100 | * Add tags to the record. |
101 | * |
102 | * @param RecordDriver $driver Driver representing record being tagged |
103 | * @param UserEntityInterface $user The user adding the tag(s) |
104 | * @param string|string[] $tags The user-provided tag(s), either as a string (to parse) or an |
105 | * array (already parsed) |
106 | * |
107 | * @return void |
108 | */ |
109 | public function linkTagsToRecord(RecordDriver $driver, UserEntityInterface $user, string|array $tags): void |
110 | { |
111 | $parsedTags = is_array($tags) ? $tags : $this->parse($tags); |
112 | $resource = $this->resourcePopulator->getOrCreateResourceForDriver($driver); |
113 | foreach ($parsedTags as $tag) { |
114 | $this->linkTagToResource($tag, $resource, $user); |
115 | } |
116 | } |
117 | |
118 | /** |
119 | * Get a tag entity if it exists; create it otherwise. |
120 | * |
121 | * @param string $tag Text of tag to fetch/create |
122 | * |
123 | * @return TagsEntityInterface |
124 | */ |
125 | public function getOrCreateTagByText(string $tag): TagsEntityInterface |
126 | { |
127 | if ($entity = $this->getTagByText($tag)) { |
128 | return $entity; |
129 | } |
130 | $newEntity = $this->tagDbService->createEntity() |
131 | ->setTag($this->caseSensitive ? $tag : mb_strtolower($tag, 'UTF8')); |
132 | $this->tagDbService->persistEntity($newEntity); |
133 | return $newEntity; |
134 | } |
135 | |
136 | /** |
137 | * Unlink a tag from a resource object. |
138 | * |
139 | * @param string $tagText Text of tag to link (empty strings will be ignored) |
140 | * @param ResourceEntityInterface|int $resourceOrId Resource entity or ID to link |
141 | * @param UserEntityInterface|int $userOrId Owner of tag link |
142 | * @param null|UserListEntityInterface|int $listOrId Optional list (omit to tag at resource level) |
143 | * |
144 | * @return void |
145 | */ |
146 | public function linkTagToResource( |
147 | string $tagText, |
148 | ResourceEntityInterface|int $resourceOrId, |
149 | UserEntityInterface|int $userOrId, |
150 | UserListEntityInterface|int|null $listOrId = null |
151 | ): void { |
152 | if (($trimmedTagText = trim($tagText)) !== '') { |
153 | $this->resourceTagsService->beginTransaction(); |
154 | $this->resourceTagsService->createLink( |
155 | $resourceOrId, |
156 | $this->getOrCreateTagByText($trimmedTagText), |
157 | $userOrId, |
158 | $listOrId |
159 | ); |
160 | $this->resourceTagsService->commitTransaction(); |
161 | } |
162 | } |
163 | |
164 | /** |
165 | * Unlink a tag from a resource object. |
166 | * |
167 | * @param string $tagText Text of tag to unlink |
168 | * @param ResourceEntityInterface|int $resourceOrId Resource entity or ID to unlink |
169 | * @param UserEntityInterface|int $userOrId Owner of tag to unlink |
170 | * @param null|UserListEntityInterface|int $listOrId Optional filter (only unlink from this list if provided) |
171 | * |
172 | * @return void |
173 | */ |
174 | public function unlinkTagFromResource( |
175 | string $tagText, |
176 | ResourceEntityInterface|int $resourceOrId, |
177 | UserEntityInterface|int $userOrId, |
178 | UserListEntityInterface|int|null $listOrId = null |
179 | ) { |
180 | $listId = $listOrId instanceof UserListEntityInterface ? $listOrId->getId() : $listOrId; |
181 | if (($trimmedTagText = trim($tagText)) !== '') { |
182 | $tagIds = []; |
183 | foreach ($this->getTagsByText($trimmedTagText) as $tag) { |
184 | $tagIds[] = $tag->getId(); |
185 | } |
186 | if ($tagIds) { |
187 | $this->resourceTagsService->destroyResourceTagsLinksForUser( |
188 | $resourceOrId instanceof ResourceEntityInterface ? $resourceOrId->getId() : $resourceOrId, |
189 | $userOrId, |
190 | $listId, |
191 | $tagIds |
192 | ); |
193 | } |
194 | } |
195 | } |
196 | |
197 | /** |
198 | * Remove tags from the record. |
199 | * |
200 | * @param RecordDriver $driver Driver representing record being tagged |
201 | * @param UserEntityInterface $user The user deleting the tag(s) |
202 | * @param string[] $tags The user-provided tag(s) |
203 | * |
204 | * @return void |
205 | */ |
206 | public function unlinkTagsFromRecord(RecordDriver $driver, UserEntityInterface $user, array $tags): void |
207 | { |
208 | $resource = $this->resourcePopulator->getOrCreateResourceForDriver($driver); |
209 | foreach ($tags as $tag) { |
210 | $this->unlinkTagFromResource($tag, $resource, $user); |
211 | } |
212 | } |
213 | |
214 | /** |
215 | * Repair duplicate tags in the database (if any). |
216 | * |
217 | * @return void |
218 | */ |
219 | public function fixDuplicateTags(): void |
220 | { |
221 | $this->getDbTable('Tags')->fixDuplicateTags($this->caseSensitive); |
222 | } |
223 | |
224 | /** |
225 | * Are tags case-sensitive? |
226 | * |
227 | * @return bool |
228 | */ |
229 | public function hasCaseSensitiveTags(): bool |
230 | { |
231 | return $this->caseSensitive; |
232 | } |
233 | |
234 | /** |
235 | * Get statistics on use of tags. |
236 | * |
237 | * @param bool $extended Include extended (unique/anonymous) stats. |
238 | * |
239 | * @return array |
240 | */ |
241 | public function getStatistics(bool $extended = false): array |
242 | { |
243 | return $this->tagDbService->getStatistics($extended, $this->caseSensitive); |
244 | } |
245 | |
246 | /** |
247 | * Get the tags that match a string |
248 | * |
249 | * @param string $text Tag to look up. |
250 | * @param string $sort Sort type |
251 | * @param int $limit Maximum results to retrieve |
252 | * |
253 | * @return array |
254 | */ |
255 | public function getNonListTagsFuzzilyMatchingString( |
256 | string $text, |
257 | string $sort = 'alphabetical', |
258 | int $limit = 100 |
259 | ): array { |
260 | return $this->tagDbService->getNonListTagsFuzzilyMatchingString($text, $sort, $limit, $this->caseSensitive); |
261 | } |
262 | |
263 | /** |
264 | * Get all matching tags by text. Normally, 0 or 1 results will be retrieved, but more |
265 | * may be retrieved under exceptional circumstances (e.g. if retrieving case-insensitively |
266 | * after storing data case-sensitively). |
267 | * |
268 | * @param string $text Tag text to match |
269 | * |
270 | * @return TagsEntityInterface[] |
271 | */ |
272 | public function getTagsByText(string $text): array |
273 | { |
274 | return $this->tagDbService->getTagsByText($text, $this->caseSensitive); |
275 | } |
276 | |
277 | /** |
278 | * Get the first available matching tag by text; return null if no match is found. |
279 | * |
280 | * @param string $text Tag text to match |
281 | * |
282 | * @return TagsEntityInterface[] |
283 | */ |
284 | public function getTagByText(string $text): ?TagsEntityInterface |
285 | { |
286 | return $this->tagDbService->getTagByText($text, $this->caseSensitive); |
287 | } |
288 | |
289 | /** |
290 | * Get all resources associated with the provided tag query. |
291 | * |
292 | * @param string $q Search query |
293 | * @param string $source Record source (optional limiter) |
294 | * @param string $sort Resource field to sort on (optional) |
295 | * @param int $offset Offset for results |
296 | * @param ?int $limit Limit for results (null for none) |
297 | * @param bool $fuzzy Are we doing an exact (false) or fuzzy (true) search? |
298 | * |
299 | * @return array |
300 | */ |
301 | public function getResourcesMatchingTagQuery( |
302 | string $q, |
303 | string $source = null, |
304 | string $sort = null, |
305 | int $offset = 0, |
306 | ?int $limit = null, |
307 | bool $fuzzy = true |
308 | ): array { |
309 | return $this->tagDbService |
310 | ->getResourcesMatchingTagQuery($q, $source, $sort, $offset, $limit, $fuzzy, $this->caseSensitive); |
311 | } |
312 | |
313 | /** |
314 | * Get a list of tags for the browse interface. |
315 | * |
316 | * @param string $sort Sort/search parameter |
317 | * @param int $limit Maximum number of tags (default = 100, < 1 = no limit) |
318 | * |
319 | * @return array |
320 | */ |
321 | public function getTagBrowseList(string $sort, int $limit): array |
322 | { |
323 | return $this->tagDbService->getTagBrowseList($sort, $limit, $this->caseSensitive); |
324 | } |
325 | |
326 | /** |
327 | * Get all tags associated with the specified record (and matching provided filters). |
328 | * |
329 | * @param string $id Record ID to look up |
330 | * @param string $source Source of record to look up |
331 | * @param int $limit Max. number of tags to return (0 = no limit) |
332 | * @param UserListEntityInterface|int|null $listOrId ID of list to load tags from (null for no restriction) |
333 | * @param UserEntityInterface|int|null $userOrId ID of user to load tags from (null for all users) |
334 | * @param string $sort Sort type ('count' or 'tag') |
335 | * @param UserEntityInterface|int|null $ownerOrId ID of user to check for ownership |
336 | * |
337 | * @return array |
338 | */ |
339 | public function getRecordTags( |
340 | string $id, |
341 | string $source = DEFAULT_SEARCH_BACKEND, |
342 | int $limit = 0, |
343 | UserListEntityInterface|int|null $listOrId = null, |
344 | UserEntityInterface|int|null $userOrId = null, |
345 | string $sort = 'count', |
346 | UserEntityInterface|int|null $ownerOrId = null |
347 | ): array { |
348 | return $this->tagDbService |
349 | ->getRecordTags($id, $source, $limit, $listOrId, $userOrId, $sort, $ownerOrId, $this->caseSensitive); |
350 | } |
351 | |
352 | /** |
353 | * Get all tags from favorite lists associated with the specified record (and matching provided filters). |
354 | * |
355 | * @param string $id Record ID to look up |
356 | * @param string $source Source of record to look up |
357 | * @param int $limit Max. number of tags to return (0 = no limit) |
358 | * @param UserListEntityInterface|int|null $listOrId ID of list to load tags from (null for tags that |
359 | * are associated with ANY list, but excluding |
360 | * non-list tags) |
361 | * @param UserEntityInterface|int|null $userOrId ID of user to load tags from (null for all users) |
362 | * @param string $sort Sort type ('count' or 'tag') |
363 | * @param UserEntityInterface|int|null $ownerOrId ID of user to check for ownership |
364 | * (this will not filter the result list, but rows owned by this user will have an is_me column set to 1) |
365 | * |
366 | * @return array |
367 | */ |
368 | public function getRecordTagsFromFavorites( |
369 | string $id, |
370 | string $source = DEFAULT_SEARCH_BACKEND, |
371 | int $limit = 0, |
372 | UserListEntityInterface|int|null $listOrId = null, |
373 | UserEntityInterface|int|null $userOrId = null, |
374 | string $sort = 'count', |
375 | UserEntityInterface|int|null $ownerOrId = null |
376 | ) { |
377 | return $this->tagDbService->getRecordTagsFromFavorites( |
378 | $id, |
379 | $source, |
380 | $limit, |
381 | $listOrId, |
382 | $userOrId, |
383 | $sort, |
384 | $ownerOrId, |
385 | $this->caseSensitive |
386 | ); |
387 | } |
388 | |
389 | /** |
390 | * Get all tags outside of favorite lists associated with the specified record (and matching provided filters). |
391 | * |
392 | * @param string $id Record ID to look up |
393 | * @param string $source Source of record to look up |
394 | * @param int $limit Max. number of tags to return (0 = no limit) |
395 | * @param UserEntityInterface|int|null $userOrId User entity/ID to load tags from (null for all users) |
396 | * @param string $sort Sort type ('count' or 'tag') |
397 | * @param UserEntityInterface|int|null $ownerOrId ID of user to check for ownership |
398 | * (this will not filter the result list, but rows owned by this user will have an is_me column set to 1) |
399 | * |
400 | * @return array |
401 | */ |
402 | public function getRecordTagsNotInFavorites( |
403 | string $id, |
404 | string $source = DEFAULT_SEARCH_BACKEND, |
405 | int $limit = 0, |
406 | UserEntityInterface|int|null $userOrId = null, |
407 | string $sort = 'count', |
408 | UserEntityInterface|int|null $ownerOrId = null |
409 | ): array { |
410 | return $this->tagDbService->getRecordTagsNotInFavorites( |
411 | $id, |
412 | $source, |
413 | $limit, |
414 | $userOrId, |
415 | $sort, |
416 | $ownerOrId, |
417 | $this->caseSensitive |
418 | ); |
419 | } |
420 | |
421 | /** |
422 | * Get a list of duplicate tags (this should never happen, but past bugs and the introduction of case-insensitive |
423 | * tags have introduced problems). |
424 | * |
425 | * @return array |
426 | */ |
427 | public function getDuplicateTags(): array |
428 | { |
429 | return $this->tagDbService->getDuplicateTags($this->caseSensitive); |
430 | } |
431 | |
432 | /** |
433 | * Get a list of all tags generated by the user in favorites lists. Note that the returned list WILL NOT include |
434 | * tags attached to records that are not saved in favorites lists. Returns an array of arrays with id and tag keys. |
435 | * |
436 | * @param UserEntityInterface|int $userOrId User ID to look up. |
437 | * @param UserListEntityInterface|int|null $listOrId Filter for tags tied to a specific list (null for no filter). |
438 | * @param ?string $recordId Filter for tags tied to a specific resource (null for no |
439 | * filter). |
440 | * @param ?string $source Filter for tags tied to a specific record source (null |
441 | * for no filter). |
442 | * |
443 | * @return array |
444 | */ |
445 | public function getUserTagsFromFavorites( |
446 | UserEntityInterface|int $userOrId, |
447 | UserListEntityInterface|int|null $listOrId = null, |
448 | ?string $recordId = null, |
449 | ?string $source = null |
450 | ): array { |
451 | return $this->tagDbService |
452 | ->getUserTagsFromFavorites($userOrId, $listOrId, $recordId, $source, $this->caseSensitive); |
453 | } |
454 | |
455 | /** |
456 | * Get tags assigned to a user list. Returns an array of arrays with id and tag keys. |
457 | * |
458 | * @param UserListEntityInterface|int $listOrId List ID or entity |
459 | * @param UserEntityInterface|int|null $userOrId User ID or entity to look up (null for no filter). |
460 | * |
461 | * @return array[] |
462 | */ |
463 | public function getListTags( |
464 | UserListEntityInterface|int $listOrId, |
465 | UserEntityInterface|int|null $userOrId = null, |
466 | ): array { |
467 | return $this->tagDbService->getListTags($listOrId, $userOrId, $this->caseSensitive); |
468 | } |
469 | |
470 | /** |
471 | * Gets unique tags from the database. |
472 | * |
473 | * @param ?int $userId ID of user (null for any) |
474 | * @param ?int $resourceId ID of the resource (null for any) |
475 | * @param ?int $tagId ID of the tag (null for any) |
476 | * |
477 | * @return array[] |
478 | */ |
479 | public function getUniqueTags( |
480 | ?int $userId = null, |
481 | ?int $resourceId = null, |
482 | ?int $tagId = null |
483 | ): array { |
484 | return $this->resourceTagsService->getUniqueTags($userId, $resourceId, $tagId, $this->caseSensitive); |
485 | } |
486 | |
487 | /** |
488 | * Get Resource Tags Paginator |
489 | * |
490 | * @param ?int $userId ID of user (null for any) |
491 | * @param ?int $resourceId ID of the resource (null for any) |
492 | * @param ?int $tagId ID of the tag (null for any) |
493 | * @param ?string $order The order in which to return the data |
494 | * @param ?int $page The page number to select |
495 | * @param int $limit The number of items to fetch |
496 | * |
497 | * @return Paginator |
498 | */ |
499 | public function getResourceTagsPaginator( |
500 | ?int $userId = null, |
501 | ?int $resourceId = null, |
502 | ?int $tagId = null, |
503 | ?string $order = null, |
504 | ?int $page = null, |
505 | int $limit = 20 |
506 | ): Paginator { |
507 | return $this->resourceTagsService |
508 | ->getResourceTagsPaginator($userId, $resourceId, $tagId, $order, $page, $limit, $this->caseSensitive); |
509 | } |
510 | |
511 | /** |
512 | * Get lists associated with a particular tag and/or list of IDs. If IDs and |
513 | * tags are both provided, only the intersection of matches will be returned. |
514 | * |
515 | * @param string|string[]|null $tag Tag or tags to match (by text, not ID; null for all) |
516 | * @param int|int[]|null $listId List ID or IDs to match (null for all) |
517 | * @param bool $publicOnly Whether to return only public lists |
518 | * @param bool $andTags Use AND operator when filtering by tag. |
519 | * |
520 | * @return UserListEntityInterface[] |
521 | */ |
522 | public function getUserListsByTagAndId( |
523 | string|array|null $tag = null, |
524 | int|array|null $listId = null, |
525 | bool $publicOnly = true, |
526 | bool $andTags = true |
527 | ): array { |
528 | return $this->userListService |
529 | ->getUserListsByTagAndId($tag, $listId, $publicOnly, $andTags, $this->caseSensitive); |
530 | } |
531 | |
532 | /** |
533 | * Delete orphaned tags (those not present in resource_tags) from the tags table. |
534 | * |
535 | * @return void |
536 | */ |
537 | public function deleteOrphanedTags(): void |
538 | { |
539 | $this->tagDbService->deleteOrphanedTags(); |
540 | } |
541 | } |