Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
8.67% |
13 / 150 |
|
9.09% |
2 / 22 |
CRAP | |
0.00% |
0 / 1 |
FavoritesService | |
8.67% |
13 / 150 |
|
9.09% |
2 / 22 |
2711.11 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createListForUser | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
destroyList | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
3.02 | |||
rememberLastUsedList | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getAndRememberListObject | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
getListIdFromParams | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getLastUsedList | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
persistToCache | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
removeListResourcesById | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
removeUserResourcesById | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
save | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
saveResourceToFavorites | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
saveRecordToFavorites | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
saveListForUser | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
addListTag | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
updateListFromRequest | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
userCanEditList | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
cacheBatch | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
saveRecordsToFavorites | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
deleteFavorites | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
getTagStringForEditing | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
formatTagStringForEditing | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | /** |
4 | * Favorites service |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2016. |
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 Favorites |
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 Main Page |
28 | */ |
29 | |
30 | namespace VuFind\Favorites; |
31 | |
32 | use DateTime; |
33 | use Laminas\Session\Container; |
34 | use Laminas\Stdlib\Parameters; |
35 | use VuFind\Db\Entity\ResourceEntityInterface; |
36 | use VuFind\Db\Entity\UserEntityInterface; |
37 | use VuFind\Db\Entity\UserListEntityInterface; |
38 | use VuFind\Db\Service\Feature\TransactionInterface; |
39 | use VuFind\Db\Service\ResourceServiceInterface; |
40 | use VuFind\Db\Service\ResourceTagsServiceInterface; |
41 | use VuFind\Db\Service\UserListServiceInterface; |
42 | use VuFind\Db\Service\UserResourceServiceInterface; |
43 | use VuFind\Db\Service\UserServiceInterface; |
44 | use VuFind\Exception\ListPermission as ListPermissionException; |
45 | use VuFind\Exception\LoginRequired as LoginRequiredException; |
46 | use VuFind\Exception\MissingField as MissingFieldException; |
47 | use VuFind\I18n\Translator\TranslatorAwareInterface; |
48 | use VuFind\Record\Cache as RecordCache; |
49 | use VuFind\Record\Loader as RecordLoader; |
50 | use VuFind\Record\ResourcePopulator; |
51 | use VuFind\RecordDriver\AbstractBase as RecordDriver; |
52 | use VuFind\Tags\TagsService; |
53 | |
54 | use function count; |
55 | use function func_get_args; |
56 | use function intval; |
57 | |
58 | /** |
59 | * Favorites service |
60 | * |
61 | * @category VuFind |
62 | * @package Favorites |
63 | * @author Demian Katz <demian.katz@villanova.edu> |
64 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
65 | * @link https://vufind.org Main Page |
66 | */ |
67 | class FavoritesService implements TranslatorAwareInterface |
68 | { |
69 | use \VuFind\I18n\Translator\TranslatorAwareTrait; |
70 | |
71 | /** |
72 | * Constructor |
73 | * |
74 | * @param ResourceServiceInterface $resourceService Resource database service |
75 | * @param ResourceTagsServiceInterface&TransactionInterface $resourceTagsService Resource tags database service |
76 | * @param UserListServiceInterface $userListService UserList database service |
77 | * @param UserResourceServiceInterface $userResourceService UserResource database service |
78 | * @param UserServiceInterface $userService User database service |
79 | * @param ResourcePopulator $resourcePopulator Resource populator service |
80 | * @param TagsService $tagsService Tags service |
81 | * @param RecordLoader $recordLoader Record loader |
82 | * @param ?RecordCache $recordCache Record cache (optional) |
83 | * @param ?Container $session Session container for remembering |
84 | * state (optional) |
85 | */ |
86 | public function __construct( |
87 | protected ResourceServiceInterface $resourceService, |
88 | protected ResourceTagsServiceInterface&TransactionInterface $resourceTagsService, |
89 | protected UserListServiceInterface $userListService, |
90 | protected UserResourceServiceInterface $userResourceService, |
91 | protected UserServiceInterface $userService, |
92 | protected ResourcePopulator $resourcePopulator, |
93 | protected TagsService $tagsService, |
94 | protected RecordLoader $recordLoader, |
95 | protected ?RecordCache $recordCache = null, |
96 | protected ?Container $session = null |
97 | ) { |
98 | } |
99 | |
100 | /** |
101 | * Create a new list object for the specified user. |
102 | * |
103 | * @param ?UserEntityInterface $user Logged in user (null if logged out) |
104 | * |
105 | * @return UserListEntityInterface |
106 | * @throws LoginRequiredException |
107 | */ |
108 | public function createListForUser(?UserEntityInterface $user): UserListEntityInterface |
109 | { |
110 | if (!$user) { |
111 | throw new LoginRequiredException('Log in to create lists.'); |
112 | } |
113 | |
114 | return $this->userListService->createEntity() |
115 | ->setCreated(new DateTime()) |
116 | ->setUser($user); |
117 | } |
118 | |
119 | /** |
120 | * Destroy a list. |
121 | * |
122 | * @param UserListEntityInterface $list List to destroy |
123 | * @param ?UserEntityInterface $user Logged-in user (null if none) |
124 | * @param bool $force Should we force the delete without checking permissions? |
125 | * |
126 | * @return void |
127 | * @throws ListPermissionException |
128 | */ |
129 | public function destroyList( |
130 | UserListEntityInterface $list, |
131 | ?UserEntityInterface $user = null, |
132 | bool $force = false |
133 | ): void { |
134 | if (!$force && !$this->userCanEditList($user, $list)) { |
135 | throw new ListPermissionException('list_access_denied'); |
136 | } |
137 | |
138 | // Remove user_resource and resource_tags rows for favorites tags: |
139 | $listUser = $list->getUser(); |
140 | $this->resourceTagsService->destroyResourceTagsLinksForUser(null, $listUser, $list); |
141 | $this->userResourceService->unlinkFavorites(null, $listUser, $list); |
142 | |
143 | // Remove resource_tags rows for list tags: |
144 | $this->resourceTagsService->destroyUserListLinks($list, $user); |
145 | |
146 | // Clean up orphaned tags: |
147 | $this->tagsService->deleteOrphanedTags(); |
148 | |
149 | $this->userListService->deleteUserList($list); |
150 | } |
151 | |
152 | /** |
153 | * Remember that this list was used so that it can become the default in |
154 | * dialog boxes. |
155 | * |
156 | * @param UserListEntityInterface $list List to remember |
157 | * |
158 | * @return void |
159 | */ |
160 | public function rememberLastUsedList(UserListEntityInterface $list): void |
161 | { |
162 | if (null !== $this->session) { |
163 | $this->session->lastUsed = $list->getId(); |
164 | } |
165 | } |
166 | |
167 | /** |
168 | * Get a list object for the specified ID (or null to create a new list). |
169 | * Ensure that the object is persisted to the database if it does not |
170 | * already exist, and remember it as the user's last-accessed list. |
171 | * |
172 | * @param ?int $listId List ID (or null to create a new list) |
173 | * @param UserEntityInterface $user The user saving the record |
174 | * |
175 | * @return UserListEntityInterface |
176 | * |
177 | * @throws \VuFind\Exception\ListPermission |
178 | */ |
179 | public function getAndRememberListObject(?int $listId, UserEntityInterface $user): UserListEntityInterface |
180 | { |
181 | if (empty($listId)) { |
182 | $list = $this->createListForUser($user) |
183 | ->setTitle($this->translate('default_list_title')); |
184 | $this->saveListForUser($list, $user); |
185 | } else { |
186 | $list = $this->userListService->getUserListById($listId); |
187 | // Validate incoming list ID: |
188 | if (!$this->userCanEditList($user, $list)) { |
189 | throw new \VuFind\Exception\ListPermission('Access denied.'); |
190 | } |
191 | $this->rememberLastUsedList($list); // handled by saveListForUser() in other case |
192 | } |
193 | return $list; |
194 | } |
195 | |
196 | /** |
197 | * Given an array of parameters, extract a list ID if possible. Return null |
198 | * if no valid ID is found or if a "NEW" record is requested. |
199 | * |
200 | * @param array $params Parameters to process |
201 | * |
202 | * @return ?int |
203 | */ |
204 | public function getListIdFromParams(array $params): ?int |
205 | { |
206 | return intval($params['list'] ?? 'NEW') ?: null; |
207 | } |
208 | |
209 | /** |
210 | * Retrieve the ID of the last list that was accessed, if any. |
211 | * |
212 | * @return ?int Identifier value of a UserListEntityInterface object (if set) or null (if not available). |
213 | */ |
214 | public function getLastUsedList(): ?int |
215 | { |
216 | return $this->session->lastUsed ?? null; |
217 | } |
218 | |
219 | /** |
220 | * Persist a resource to the record cache (if applicable). |
221 | * |
222 | * @param RecordDriver $driver Record driver to persist |
223 | * @param ResourceEntityInterface $resource Resource row |
224 | * |
225 | * @return void |
226 | */ |
227 | protected function persistToCache( |
228 | RecordDriver $driver, |
229 | ResourceEntityInterface $resource |
230 | ) { |
231 | if ($this->recordCache) { |
232 | $this->recordCache->setContext(RecordCache::CONTEXT_FAVORITE); |
233 | $this->recordCache->createOrUpdate( |
234 | $resource->getRecordId(), |
235 | $resource->getSource(), |
236 | $driver->getRawData() |
237 | ); |
238 | } |
239 | } |
240 | |
241 | /** |
242 | * Given an array of item ids, remove them from the specified list. |
243 | * |
244 | * @param UserListEntityInterface $list List being updated |
245 | * @param ?UserEntityInterface $user Logged-in user (null if none) |
246 | * @param string[] $ids IDs to remove from the list |
247 | * @param string $source Type of resource identified by IDs |
248 | * |
249 | * @return void |
250 | */ |
251 | public function removeListResourcesById( |
252 | UserListEntityInterface $list, |
253 | ?UserEntityInterface $user, |
254 | array $ids, |
255 | string $source = DEFAULT_SEARCH_BACKEND |
256 | ): void { |
257 | if (!$this->userCanEditList($user, $list)) { |
258 | throw new ListPermissionException('list_access_denied'); |
259 | } |
260 | |
261 | // Retrieve a list of resource IDs: |
262 | $resources = $this->resourceService->getResourcesByRecordIds($ids, $source); |
263 | |
264 | $resourceIDs = []; |
265 | foreach ($resources as $current) { |
266 | $resourceIDs[] = $current->getId(); |
267 | } |
268 | |
269 | // Remove Resource and related tags: |
270 | $listUser = $list->getUser(); |
271 | $this->resourceTagsService->destroyResourceTagsLinksForUser($resourceIDs, $listUser, $list); |
272 | $this->userResourceService->unlinkFavorites($resourceIDs, $listUser, $list); |
273 | $this->tagsService->deleteOrphanedTags(); |
274 | } |
275 | |
276 | /** |
277 | * Given an array of item ids, remove them from all of the specified user's lists |
278 | * |
279 | * @param UserEntityInterface $user User owning lists |
280 | * @param string[] $ids IDs to remove from the list |
281 | * @param string $source Type of resource identified by IDs |
282 | * |
283 | * @return void |
284 | */ |
285 | public function removeUserResourcesById( |
286 | UserEntityInterface $user, |
287 | array $ids, |
288 | $source = DEFAULT_SEARCH_BACKEND |
289 | ): void { |
290 | // Retrieve a list of resource IDs: |
291 | $resources = $this->resourceService->getResourcesByRecordIds($ids, $source); |
292 | |
293 | $resourceIDs = []; |
294 | foreach ($resources as $current) { |
295 | $resourceIDs[] = $current->getId(); |
296 | } |
297 | |
298 | // Remove resource and related tags: |
299 | $this->resourceTagsService->destroyAllListResourceTagsLinksForUser($resourceIDs, $user); |
300 | $this->userResourceService->unlinkFavorites($resourceIDs, $user->getId(), null); |
301 | $this->tagsService->deleteOrphanedTags(); |
302 | } |
303 | |
304 | /** |
305 | * Legacy name for saveRecordToFavorites() |
306 | * |
307 | * @return array |
308 | * |
309 | * @deprecated Use saveRecordToFavorites() |
310 | */ |
311 | public function save() |
312 | { |
313 | return $this->saveRecordToFavorites(...func_get_args()); |
314 | } |
315 | |
316 | /** |
317 | * Add/update a resource in the user's account. |
318 | * |
319 | * @param UserEntityInterface|int $userOrId The user entity or ID saving the favorites |
320 | * @param ResourceEntityInterface|int $resourceOrId The resource entity or ID to add/update |
321 | * @param UserListEntityInterface|int $listOrId The list entity or ID to store the resource in. |
322 | * @param array $tagArray An array of tags to associate with the resource. |
323 | * @param string $notes User notes about the resource. |
324 | * @param bool $replaceExisting Whether to replace all existing tags (true) or |
325 | * append to the existing list (false). |
326 | * |
327 | * @return void |
328 | */ |
329 | public function saveResourceToFavorites( |
330 | UserEntityInterface|int $userOrId, |
331 | ResourceEntityInterface|int $resourceOrId, |
332 | UserListEntityInterface|int $listOrId, |
333 | array $tagArray, |
334 | string $notes, |
335 | bool $replaceExisting = true |
336 | ): void { |
337 | $user = $userOrId instanceof UserEntityInterface |
338 | ? $userOrId |
339 | : $this->userService->getUserById($userOrId); |
340 | $resource = $resourceOrId instanceof ResourceEntityInterface |
341 | ? $resourceOrId |
342 | : $this->resourceService->getResourceById($resourceOrId); |
343 | $list = $listOrId instanceof UserListEntityInterface |
344 | ? $listOrId |
345 | : $this->userListService->getUserListById($listOrId); |
346 | |
347 | // Create the resource link if it doesn't exist and update the notes in any |
348 | // case: |
349 | $this->userResourceService->createOrUpdateLink($resource, $user, $list, $notes); |
350 | |
351 | // If we're replacing existing tags, delete the old ones before adding the new ones: |
352 | if ($replaceExisting) { |
353 | $this->resourceTagsService->destroyResourceTagsLinksForUser($resource->getId(), $user, $list); |
354 | } |
355 | |
356 | // Add the new tags: |
357 | foreach ($tagArray as $tag) { |
358 | $this->tagsService->linkTagToResource($tag, $resource, $user, $list); |
359 | } |
360 | } |
361 | |
362 | /** |
363 | * Save this record to the user's favorites. |
364 | * |
365 | * @param array $params Array with some or all of these keys: |
366 | * <ul> |
367 | * <li>mytags - Tag array to associate with record (optional)</li> |
368 | * <li>notes - Notes to associate with record (optional)</li> |
369 | * <li>list - ID of list to save record into (omit to create new list)</li> |
370 | * </ul> |
371 | * @param UserEntityInterface $user The user saving the record |
372 | * @param RecordDriver $driver Record driver for record being saved |
373 | * |
374 | * @return array list information |
375 | */ |
376 | public function saveRecordToFavorites( |
377 | array $params, |
378 | UserEntityInterface $user, |
379 | RecordDriver $driver |
380 | ): array { |
381 | // Validate incoming parameters: |
382 | if (!$user) { |
383 | throw new LoginRequiredException('You must be logged in first'); |
384 | } |
385 | |
386 | // Get or create a list object as needed: |
387 | $list = $this->getAndRememberListObject($this->getListIdFromParams($params), $user); |
388 | |
389 | // Get or create a resource object as needed: |
390 | $resource = $this->resourcePopulator->getOrCreateResourceForDriver($driver); |
391 | |
392 | // Persist record in the database for "offline" use |
393 | $this->persistToCache($driver, $resource); |
394 | |
395 | // Add the information to the user's account: |
396 | $this->saveResourceToFavorites( |
397 | $user, |
398 | $resource, |
399 | $list, |
400 | $params['mytags'] ?? [], |
401 | $params['notes'] ?? '' |
402 | ); |
403 | return ['listId' => $list->getId()]; |
404 | } |
405 | |
406 | /** |
407 | * Saves the provided list to the database and remembers it in the session if it is valid; |
408 | * throws an exception otherwise. |
409 | * |
410 | * @param UserListEntityInterface $list List to save |
411 | * @param ?UserEntityInterface $user Logged-in user (null if none) |
412 | * |
413 | * @return void |
414 | * @throws ListPermissionException |
415 | * @throws MissingFieldException |
416 | */ |
417 | public function saveListForUser(UserListEntityInterface $list, ?UserEntityInterface $user): void |
418 | { |
419 | if (!$this->userCanEditList($user, $list)) { |
420 | throw new ListPermissionException('list_access_denied'); |
421 | } |
422 | if (!$list->getTitle()) { |
423 | throw new MissingFieldException('list_edit_name_required'); |
424 | } |
425 | |
426 | $this->userListService->persistEntity($list); |
427 | $this->rememberLastUsedList($list); |
428 | } |
429 | |
430 | /** |
431 | * Add a tag to a list. |
432 | * |
433 | * @param string $tagText The tag to save. |
434 | * @param UserListEntityInterface $list The list being tagged. |
435 | * @param UserEntityInterface $user The user posting the tag. |
436 | * |
437 | * @return void |
438 | */ |
439 | public function addListTag(string $tagText, UserListEntityInterface $list, UserEntityInterface $user): void |
440 | { |
441 | $tagText = trim($tagText); |
442 | if (!empty($tagText)) { |
443 | // Create and link tag in a transaction so we don't accidentally purge it as an orphan: |
444 | $this->resourceTagsService->beginTransaction(); |
445 | $tag = $this->tagsService->getOrCreateTagByText($tagText); |
446 | $this->resourceTagsService->createLink(null, $tag, $user, $list); |
447 | $this->resourceTagsService->commitTransaction(); |
448 | } |
449 | } |
450 | |
451 | /** |
452 | * Update and save the list object using a request object -- useful for |
453 | * sharing form processing between multiple actions. |
454 | * |
455 | * @param UserListEntityInterface $list List to update |
456 | * @param ?UserEntityInterface $user Logged-in user (false if none) |
457 | * @param Parameters $request Request to process |
458 | * |
459 | * @return int ID of newly created row |
460 | * @throws ListPermissionException |
461 | * @throws MissingFieldException |
462 | */ |
463 | public function updateListFromRequest( |
464 | UserListEntityInterface $list, |
465 | ?UserEntityInterface $user, |
466 | Parameters $request |
467 | ): int { |
468 | $list->setTitle($request->get('title')) |
469 | ->setDescription($request->get('desc')) |
470 | ->setPublic((bool)$request->get('public')); |
471 | $this->saveListForUser($list, $user); |
472 | |
473 | if (null !== ($tags = $request->get('tags'))) { |
474 | $this->resourceTagsService->destroyUserListLinks($list, $user); |
475 | foreach ($this->tagsService->parse($tags) as $tag) { |
476 | $this->addListTag($tag, $list, $user); |
477 | } |
478 | } |
479 | |
480 | return $list->getId(); |
481 | } |
482 | |
483 | /** |
484 | * Is the provided user allowed to edit the provided list? |
485 | * |
486 | * @param ?UserEntityInterface $user Logged-in user (null if none) |
487 | * @param UserListEntityInterface $list List to check |
488 | * |
489 | * @return bool |
490 | */ |
491 | public function userCanEditList(?UserEntityInterface $user, UserListEntityInterface $list): bool |
492 | { |
493 | return $user && $user->getId() === $list->getUser()?->getId(); |
494 | } |
495 | |
496 | /** |
497 | * Support method for saveBulk() -- save a batch of records to the cache. |
498 | * |
499 | * @param array $cacheRecordIds Array of IDs in source|id format |
500 | * |
501 | * @return void |
502 | */ |
503 | protected function cacheBatch(array $cacheRecordIds) |
504 | { |
505 | if ($cacheRecordIds && $this->recordCache) { |
506 | // Disable the cache so that we fetch latest versions, not cached ones: |
507 | $this->recordLoader->setCacheContext(RecordCache::CONTEXT_DISABLED); |
508 | $records = $this->recordLoader->loadBatch($cacheRecordIds); |
509 | // Re-enable the cache so that we actually save the records: |
510 | $this->recordLoader->setCacheContext(RecordCache::CONTEXT_FAVORITE); |
511 | foreach ($records as $record) { |
512 | $this->recordCache->createOrUpdate( |
513 | $record->getUniqueID(), |
514 | $record->getSourceIdentifier(), |
515 | $record->getRawData() |
516 | ); |
517 | } |
518 | } |
519 | } |
520 | |
521 | /** |
522 | * Save a group of records to the user's favorites. |
523 | * |
524 | * @param array $params Array with some or all of these keys: |
525 | * <ul> <li>ids - Array of IDs in |
526 | * source|id format</li> <li>mytags - |
527 | * Unparsed tag string to associate with |
528 | * record (optional)</li> <li>list - ID |
529 | * of list to save record into (omit to |
530 | * create new list)</li> </ul> |
531 | * @param UserEntityInterface $user The user saving the record |
532 | * |
533 | * @return array list information |
534 | */ |
535 | public function saveRecordsToFavorites(array $params, UserEntityInterface $user): array |
536 | { |
537 | // Load helper objects needed for the saving process: |
538 | $list = $this->getAndRememberListObject($this->getListIdFromParams($params), $user); |
539 | $this->recordCache?->setContext(RecordCache::CONTEXT_FAVORITE); |
540 | |
541 | $cacheRecordIds = []; // list of record IDs to save to cache |
542 | foreach ($params['ids'] as $current) { |
543 | // Break apart components of ID: |
544 | [$source, $id] = explode('|', $current, 2); |
545 | |
546 | // Get or create a resource object as needed: |
547 | $resource = $this->resourcePopulator->getOrCreateResourceForRecordId($id, $source); |
548 | |
549 | // Add the information to the user's account: |
550 | $tags = isset($params['mytags']) ? $this->tagsService->parse($params['mytags']) : []; |
551 | $this->saveResourceToFavorites($user, $resource, $list, $tags, '', false); |
552 | |
553 | // Collect record IDs for caching |
554 | if ($this->recordCache?->isCachable($resource->getSource())) { |
555 | $cacheRecordIds[] = $current; |
556 | } |
557 | } |
558 | |
559 | $this->cacheBatch($cacheRecordIds); |
560 | return ['listId' => $list->getId()]; |
561 | } |
562 | |
563 | /** |
564 | * Delete a group of favorites. |
565 | * |
566 | * @param string[] $ids Array of IDs in source|id format. |
567 | * @param ?int $listID ID of list to delete from (null for all lists) |
568 | * @param UserEntityInterface $user Logged in user |
569 | * |
570 | * @return void |
571 | */ |
572 | public function deleteFavorites(array $ids, ?int $listID, UserEntityInterface $user): void |
573 | { |
574 | // Sort $ids into useful array: |
575 | $sorted = []; |
576 | foreach ($ids as $current) { |
577 | [$source, $id] = explode('|', $current, 2); |
578 | if (!isset($sorted[$source])) { |
579 | $sorted[$source] = []; |
580 | } |
581 | $sorted[$source][] = $id; |
582 | } |
583 | |
584 | // Delete favorites one source at a time, using a different object depending |
585 | // on whether we are working with a list or user favorites. |
586 | if (empty($listID)) { |
587 | foreach ($sorted as $source => $ids) { |
588 | $this->removeUserResourcesById($user, $ids, $source); |
589 | } |
590 | } else { |
591 | $list = $this->userListService->getUserListById($listID); |
592 | foreach ($sorted as $source => $ids) { |
593 | $this->removeListResourcesById($list, $user, $ids, $source); |
594 | } |
595 | } |
596 | } |
597 | |
598 | /** |
599 | * Call TagsService::getUserTagsFromFavorites() and format the results for editing. |
600 | * |
601 | * @param UserEntityInterface|int $userOrId User ID to look up. |
602 | * @param UserListEntityInterface|int|null $listOrId Filter for tags tied to a specific list (null for no |
603 | * filter). |
604 | * @param ?string $recordId Filter for tags tied to a specific resource (null for no |
605 | * filter). |
606 | * @param ?string $source Filter for tags tied to a specific record source (null for |
607 | * no filter). |
608 | * |
609 | * @return string |
610 | */ |
611 | public function getTagStringForEditing( |
612 | UserEntityInterface|int $userOrId, |
613 | UserListEntityInterface|int|null $listOrId = null, |
614 | ?string $recordId = null, |
615 | ?string $source = null |
616 | ): string { |
617 | return $this->formatTagStringForEditing( |
618 | $this->tagsService->getUserTagsFromFavorites( |
619 | $userOrId, |
620 | $listOrId, |
621 | $recordId, |
622 | $source |
623 | ) |
624 | ); |
625 | } |
626 | |
627 | /** |
628 | * Convert an array representing tags into a string for an edit form |
629 | * |
630 | * @param array $tags Tags |
631 | * |
632 | * @return string |
633 | */ |
634 | public function formatTagStringForEditing($tags): string |
635 | { |
636 | $tagStr = ''; |
637 | if (count($tags) > 0) { |
638 | foreach ($tags as $tag) { |
639 | if (strstr($tag['tag'], ' ')) { |
640 | $tagStr .= "\"{$tag['tag']}\" "; |
641 | } else { |
642 | $tagStr .= "{$tag['tag']} "; |
643 | } |
644 | } |
645 | } |
646 | return trim($tagStr); |
647 | } |
648 | } |