Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
15.79% covered (danger)
15.79%
6 / 38
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserListService
15.79% covered (danger)
15.79%
6 / 38
25.00% covered (danger)
25.00%
2 / 8
189.58
0.00% covered (danger)
0.00%
0 / 1
 createEntity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 deleteUserList
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getUserListById
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getPublicLists
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getUserListsAndCountsByUser
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getUserListsByTagAndId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getUserListsByUser
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getListsContainingRecord
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * Database service for UserList.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2023.
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  Database
25 * @author   Sudharma Kellampalli <skellamp@villanova.edu>
26 * @author   Demian Katz <demian.katz@villanova.edu>
27 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
28 * @link     https://vufind.org/wiki/development:plugins:database_gateways Wiki
29 */
30
31namespace VuFind\Db\Service;
32
33use Exception;
34use Laminas\Db\Sql\Expression;
35use Laminas\Db\Sql\ExpressionInterface;
36use Laminas\Db\Sql\Select;
37use VuFind\Db\Entity\UserEntityInterface;
38use VuFind\Db\Entity\UserListEntityInterface;
39use VuFind\Db\Table\DbTableAwareInterface;
40use VuFind\Db\Table\DbTableAwareTrait;
41use VuFind\Exception\RecordMissing as RecordMissingException;
42
43use function is_int;
44
45/**
46 * Database service for UserList.
47 *
48 * @category VuFind
49 * @package  Database
50 * @author   Sudharma Kellampalli <skellamp@villanova.edu>
51 * @author   Demian Katz <demian.katz@villanova.edu>
52 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
53 * @link     https://vufind.org/wiki/development:plugins:database_gateways Wiki
54 */
55class UserListService extends AbstractDbService implements DbTableAwareInterface, UserListServiceInterface
56{
57    use DbTableAwareTrait;
58
59    /**
60     * Create a UserList entity object.
61     *
62     * @return UserListEntityInterface
63     */
64    public function createEntity(): UserListEntityInterface
65    {
66        return $this->getDbTable('UserList')->createRow();
67    }
68
69    /**
70     * Delete a user list entity.
71     *
72     * @param UserListEntityInterface|int $listOrId List entity object or ID to delete
73     *
74     * @return void
75     */
76    public function deleteUserList(UserListEntityInterface|int $listOrId): void
77    {
78        $listId = $listOrId instanceof UserListEntityInterface ? $listOrId->getId() : $listOrId;
79        $this->getDbTable('UserList')->delete(['id' => $listId]);
80    }
81
82    /**
83     * Retrieve a list object.
84     *
85     * @param int $id Numeric ID for existing list.
86     *
87     * @return UserListEntityInterface
88     * @throws RecordMissingException
89     */
90    public function getUserListById(int $id): UserListEntityInterface
91    {
92        $result = $this->getDbTable('UserList')->select(['id' => $id])->current();
93        if (empty($result)) {
94            throw new RecordMissingException('Cannot load list ' . $id);
95        }
96        return $result;
97    }
98
99    /**
100     * Get public lists.
101     *
102     * @param array $includeFilter List of list ids or entities to include in result.
103     * @param array $excludeFilter List of list ids or entities to exclude from result.
104     *
105     * @return UserListEntityInterface[]
106     */
107    public function getPublicLists(array $includeFilter = [], array $excludeFilter = []): array
108    {
109        $callback = function ($listOrId) {
110            return $listOrId instanceof UserListEntityInterface ? $listOrId->getId() : $listOrId;
111        };
112        $includeIds = array_map($callback, $includeFilter);
113        $excludeIds = array_map($callback, $excludeFilter);
114        $callback = function ($select) use ($includeIds, $excludeIds) {
115            $select->where->equalTo('public', 1);
116            if ($excludeIds) {
117                $select->where->notIn('id', $excludeIds);
118            }
119            if ($includeIds) {
120                $select->where->in('id', $includeIds);
121            }
122        };
123        return iterator_to_array($this->getDbTable('UserList')->select($callback));
124    }
125
126    /**
127     * Get lists belonging to the user and their count. Returns an array of arrays with
128     * list_entity and count keys.
129     *
130     * @param UserEntityInterface|int $userOrId User entity object or ID
131     *
132     * @return array
133     * @throws Exception
134     */
135    public function getUserListsAndCountsByUser(UserEntityInterface|int $userOrId): array
136    {
137        $userId = $userOrId instanceof UserEntityInterface ? $userOrId->getId() : $userOrId;
138        $callback = function (Select $select) use ($userId) {
139            $select->columns(
140                [
141                    Select::SQL_STAR,
142                    'cnt' => new Expression(
143                        'COUNT(DISTINCT(?))',
144                        ['ur.resource_id'],
145                        [ExpressionInterface::TYPE_IDENTIFIER]
146                    ),
147                ]
148            );
149            $select->join(
150                ['ur' => 'user_resource'],
151                'user_list.id = ur.list_id',
152                [],
153                $select::JOIN_LEFT
154            );
155            $select->where->equalTo('user_list.user_id', $userId);
156            $select->group(
157                [
158                    'user_list.id', 'user_list.user_id', 'title', 'description',
159                    'created', 'public',
160                ]
161            );
162            $select->order(['title']);
163        };
164
165        $result = [];
166        foreach ($this->getDbTable('UserList')->select($callback) as $row) {
167            $result[] = ['list_entity' => $row, 'count' => $row->cnt];
168        }
169        return $result;
170    }
171
172    /**
173     * Get lists associated with a particular tag and/or list of IDs. If IDs and
174     * tags are both provided, only the intersection of matches will be returned.
175     *
176     * @param string|string[]|null $tag               Tag or tags to match (by text, not ID; null for all)
177     * @param int|int[]|null       $listId            List ID or IDs to match (null for all)
178     * @param bool                 $publicOnly        Whether to return only public lists
179     * @param bool                 $andTags           Use AND operator when filtering by tag.
180     * @param bool                 $caseSensitiveTags Should we treat tags case-sensitively?
181     *
182     * @return UserListEntityInterface[]
183     */
184    public function getUserListsByTagAndId(
185        string|array|null $tag = null,
186        int|array|null $listId = null,
187        bool $publicOnly = true,
188        bool $andTags = true,
189        bool $caseSensitiveTags = false
190    ): array {
191        $lists = $this->getDbTable('ResourceTags')
192            ->getListsForTag($tag, $listId, $publicOnly, $andTags, $caseSensitiveTags);
193        $listIds = array_column(iterator_to_array($lists), 'list_id');
194        $callback = function ($select) use ($listIds) {
195            $select->where->in('id', $listIds);
196        };
197        return iterator_to_array($this->getDbTable('UserList')->select($callback));
198    }
199
200    /**
201     * Get list objects belonging to the specified user.
202     *
203     * @param UserEntityInterface|int $userOrId User entity object or ID
204     *
205     * @return UserListEntityInterface[]
206     */
207    public function getUserListsByUser(UserEntityInterface|int $userOrId): array
208    {
209        $userId = $userOrId instanceof UserEntityInterface ? $userOrId->getId() : $userOrId;
210        $callback = function ($select) use ($userId) {
211            $select->where->equalTo('user_id', $userId);
212            $select->order(['title']);
213        };
214        return iterator_to_array($this->getDbTable('UserList')->select($callback));
215    }
216
217    /**
218     * Get lists containing a specific record.
219     *
220     * @param string                       $recordId ID of record being checked.
221     * @param string                       $source   Source of record to look up
222     * @param UserEntityInterface|int|null $userOrId Optional user ID or entity object (to limit results
223     * to a particular user).
224     *
225     * @return UserListEntityInterface[]
226     */
227    public function getListsContainingRecord(
228        string $recordId,
229        string $source = DEFAULT_SEARCH_BACKEND,
230        UserEntityInterface|int|null $userOrId = null
231    ): array {
232        return iterator_to_array(
233            $this->getDbTable('UserList')->getListsContainingResource(
234                $recordId,
235                $source,
236                is_int($userOrId) ? $userOrId : $userOrId->getId()
237            )
238        );
239    }
240}