Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
2.83% |
3 / 106 |
|
10.00% |
1 / 10 |
CRAP | |
0.00% |
0 / 1 |
UserCardService | |
2.83% |
3 / 106 |
|
10.00% |
1 / 10 |
1739.41 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getInsecureRows | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllRowsWithUsernames | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getLibraryCards | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
getOrCreateLibraryCard | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
deleteLibraryCard | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
56 | |||
persistLibraryCardData | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
210 | |||
synchronizeUserLibraryCardData | |
11.11% |
2 / 18 |
|
0.00% |
0 / 1 |
22.56 | |||
activateLibraryCard | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
createEntity | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * Database service for UserCard. |
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 | |
31 | namespace VuFind\Db\Service; |
32 | |
33 | use DateTime; |
34 | use VuFind\Auth\ILSAuthenticator; |
35 | use VuFind\Config\AccountCapabilities; |
36 | use VuFind\Db\Entity\UserCardEntityInterface; |
37 | use VuFind\Db\Entity\UserEntityInterface; |
38 | use VuFind\Db\Table\DbTableAwareInterface; |
39 | use VuFind\Db\Table\DbTableAwareTrait; |
40 | |
41 | use function count; |
42 | use function is_int; |
43 | |
44 | /** |
45 | * Database service for UserCard. |
46 | * |
47 | * @category VuFind |
48 | * @package Database |
49 | * @author Demian Katz <demian.katz@villanova.edu> |
50 | * @author Demian Katz <demian.katz@villanova.edu> |
51 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
52 | * @link https://vufind.org/wiki/development:plugins:database_gateways Wiki |
53 | */ |
54 | class UserCardService extends AbstractDbService implements |
55 | DbServiceAwareInterface, |
56 | DbTableAwareInterface, |
57 | UserCardServiceInterface |
58 | { |
59 | use DbServiceAwareTrait; |
60 | use DbTableAwareTrait; |
61 | |
62 | /** |
63 | * Constructor |
64 | * |
65 | * @param ILSAuthenticator $ilsAuthenticator ILS authenticator |
66 | * @param AccountCapabilities $capabilities Account capabilities configuration |
67 | */ |
68 | public function __construct( |
69 | protected ILSAuthenticator $ilsAuthenticator, |
70 | protected AccountCapabilities $capabilities |
71 | ) { |
72 | } |
73 | |
74 | /** |
75 | * Get user_card rows with insecure catalog passwords. |
76 | * |
77 | * @return UserCardEntityInterface[] |
78 | */ |
79 | public function getInsecureRows(): array |
80 | { |
81 | return iterator_to_array($this->getDbTable('UserCard')->getInsecureRows()); |
82 | } |
83 | |
84 | /** |
85 | * Get user_card rows with catalog usernames set. |
86 | * |
87 | * @return UserCardEntityInterface[] |
88 | */ |
89 | public function getAllRowsWithUsernames(): array |
90 | { |
91 | $callback = function ($select) { |
92 | $select->where->isNotNull('cat_username'); |
93 | }; |
94 | return iterator_to_array($this->getDbTable('UserCard')->select($callback)); |
95 | } |
96 | |
97 | /** |
98 | * Get all library cards associated with the user. |
99 | * |
100 | * @param UserEntityInterface|int $userOrId User object or identifier |
101 | * @param ?int $id Optional card ID filter |
102 | * @param ?string $catUsername Optional catalog username filter |
103 | * |
104 | * @return UserCardEntityInterface[] |
105 | */ |
106 | public function getLibraryCards( |
107 | UserEntityInterface|int $userOrId, |
108 | ?int $id = null, |
109 | ?string $catUsername = null |
110 | ): array { |
111 | if (!$this->capabilities->libraryCardsEnabled()) { |
112 | return []; |
113 | } |
114 | $userCard = $this->getDbTable('UserCard'); |
115 | $criteria = [ |
116 | 'user_id' => is_int($userOrId) ? $userOrId : $userOrId->getId(), |
117 | ]; |
118 | if ($id) { |
119 | $criteria['id'] = $id; |
120 | } |
121 | if ($catUsername) { |
122 | $criteria['cat_username'] = $catUsername; |
123 | } |
124 | return iterator_to_array($userCard->select($criteria)); |
125 | } |
126 | |
127 | /** |
128 | * Get or create library card data. |
129 | * |
130 | * @param UserEntityInterface|int $userOrId User object or identifier |
131 | * @param ?int $id Card ID to fetch (or null to create a new card) |
132 | * |
133 | * @return UserCardEntityInterface Card data if found; throws exception otherwise |
134 | * @throws \VuFind\Exception\LibraryCard |
135 | */ |
136 | public function getOrCreateLibraryCard(UserEntityInterface|int $userOrId, ?int $id = null): UserCardEntityInterface |
137 | { |
138 | if (!$this->capabilities->libraryCardsEnabled()) { |
139 | throw new \VuFind\Exception\LibraryCard('Library Cards Disabled'); |
140 | } |
141 | |
142 | if ($id === null) { |
143 | $user = is_int($userOrId) |
144 | ? $this->getDbService(UserServiceInterface::class)->getUserById($userOrId) : $userOrId; |
145 | $row = $this->createEntity() |
146 | ->setCardName('') |
147 | ->setUser($user) |
148 | ->setCatUsername('') |
149 | ->setRawCatPassword(''); |
150 | } else { |
151 | $row = current($this->getLibraryCards($userOrId, $id)); |
152 | if ($row === false) { |
153 | throw new \VuFind\Exception\LibraryCard('Library Card Not Found'); |
154 | } |
155 | } |
156 | return $row; |
157 | } |
158 | |
159 | /** |
160 | * Delete library card. |
161 | * |
162 | * @param UserEntityInterface $user User owning card to delete |
163 | * @param UserCardEntityInterface|int $userCard UserCard id or object to be deleted |
164 | * |
165 | * @return bool |
166 | * @throws \Exception |
167 | */ |
168 | public function deleteLibraryCard(UserEntityInterface $user, UserCardEntityInterface|int $userCard): bool |
169 | { |
170 | if (!$this->capabilities->libraryCardsEnabled()) { |
171 | throw new \VuFind\Exception\LibraryCard('Library Cards Disabled'); |
172 | } |
173 | $cardId = is_int($userCard) ? $userCard : $userCard->getId(); |
174 | $row = current($this->getLibraryCards($user, $cardId)); |
175 | if (!$row) { |
176 | throw new \Exception('Library card not found'); |
177 | } |
178 | if (!$row instanceof \VuFind\Db\Row\UserCard) { |
179 | $row = $this->getDbTable('UserCard')->select(['id' => $cardId])->current(); |
180 | } |
181 | $row->delete(); |
182 | |
183 | if ($row->getCatUsername() == $user->getCatUsername()) { |
184 | // Activate another card (if any) or remove cat_username and cat_password |
185 | $cards = $this->getLibraryCards($user); |
186 | if (count($cards) > 0) { |
187 | $this->activateLibraryCard($user, current($cards)->getId()); |
188 | } else { |
189 | $user->setCatUsername(null); |
190 | $user->setRawCatPassword(null); |
191 | $user->setCatPassEnc(null); |
192 | $this->persistEntity($user); |
193 | } |
194 | } |
195 | |
196 | return true; |
197 | } |
198 | |
199 | /** |
200 | * Persist the provided library card data, either by updating a specified card |
201 | * or by creating a new one (when $card is null). Also updates the primary user |
202 | * row when appropriate. Will throw an exception if a duplicate $username value |
203 | * is provided; there should only be one card row per username. |
204 | * |
205 | * Returns the row that was added or updated. |
206 | * |
207 | * @param UserEntityInterface|int $userOrId User object or identifier |
208 | * @param UserCardEntityInterface|int|null $cardOrId Card entity or ID (null = create new) |
209 | * @param string $cardName Card name |
210 | * @param string $username Username |
211 | * @param string $password Password |
212 | * @param string $homeLib Home Library |
213 | * |
214 | * @return UserCardEntityInterface |
215 | * @throws \VuFind\Exception\LibraryCard |
216 | */ |
217 | public function persistLibraryCardData( |
218 | UserEntityInterface|int $userOrId, |
219 | UserCardEntityInterface|int|null $cardOrId, |
220 | string $cardName, |
221 | string $username, |
222 | string $password, |
223 | string $homeLib = '' |
224 | ): UserCardEntityInterface { |
225 | if (!$this->capabilities->libraryCardsEnabled()) { |
226 | throw new \VuFind\Exception\LibraryCard('Library Cards Disabled'); |
227 | } |
228 | // Extract a card ID, if available: |
229 | $id = $cardOrId instanceof UserCardEntityInterface ? $cardOrId->getId() : $cardOrId; |
230 | // Check that the username is not already in use in another card |
231 | $usernameCheck = current($this->getLibraryCards($userOrId, catUsername: $username)); |
232 | if (!empty($usernameCheck) && ($id === null || $usernameCheck->getId() != $id)) { |
233 | throw new \VuFind\Exception\LibraryCard( |
234 | 'Username is already in use in another library card' |
235 | ); |
236 | } |
237 | |
238 | $user = is_int($userOrId) |
239 | ? $this->getDbService(UserServiceInterface::class)->getUserById($userOrId) : $userOrId; |
240 | |
241 | $row = ($id !== null) ? current($this->getLibraryCards($user, $id)) : null; |
242 | if (empty($row)) { |
243 | $row = $this->createEntity() |
244 | ->setUser($user) |
245 | ->setCreated(new DateTime()); |
246 | } |
247 | $row->setCardName($cardName); |
248 | $row->setCatUsername($username); |
249 | if (!empty($homeLib)) { |
250 | $row->setHomeLibrary($homeLib); |
251 | } |
252 | if ($this->ilsAuthenticator->passwordEncryptionEnabled()) { |
253 | $row->setRawCatPassword(null); |
254 | $row->setCatPassEnc($this->ilsAuthenticator->encrypt($password)); |
255 | } else { |
256 | $row->setRawCatPassword($password); |
257 | $row->setCatPassEnc(null); |
258 | } |
259 | |
260 | $this->persistEntity($row); |
261 | |
262 | // If this is the first or active library card, or no credentials are |
263 | // currently set, activate the card now |
264 | if ( |
265 | count($this->getLibraryCards($user)) == 1 || !$user->getCatUsername() |
266 | || $user->getCatUsername() === $row->getCatUsername() |
267 | ) { |
268 | $this->activateLibraryCard($user, $row->getId()); |
269 | } |
270 | |
271 | return $row; |
272 | } |
273 | |
274 | /** |
275 | * Verify that the user's current ILS settings exist in their library card data |
276 | * (if enabled) and are up to date. Designed to be called after updating the |
277 | * user row; will create or modify library card rows as needed. |
278 | * |
279 | * @param UserEntityInterface|int $userOrId User object or identifier |
280 | * |
281 | * @return bool |
282 | * @throws \VuFind\Exception\PasswordSecurity |
283 | */ |
284 | public function synchronizeUserLibraryCardData(UserEntityInterface|int $userOrId): bool |
285 | { |
286 | if (!$this->capabilities->libraryCardsEnabled()) { |
287 | return true; // success, because there's nothing to do |
288 | } |
289 | $user = is_int($userOrId) |
290 | ? $this->getDbService(UserServiceInterface::class)->getUserById($userOrId) : $userOrId; |
291 | if (!$user->getCatUsername()) { |
292 | return true; // success, because there's nothing to do |
293 | } |
294 | $row = current($this->getLibraryCards($user, catUsername: $user->getCatUsername())); |
295 | if (empty($row)) { |
296 | $row = $this->createEntity() |
297 | ->setUser($user) |
298 | ->setCatUsername($user->getCatUsername()) |
299 | ->setCardName($user->getCatUsername()) |
300 | ->setCreated(new DateTime()); |
301 | } |
302 | // Always update home library and password |
303 | $row->setHomeLibrary($user->getHomeLibrary()); |
304 | $row->setRawCatPassword($user->getRawCatPassword()); |
305 | $row->setCatPassEnc($user->getCatPassEnc()); |
306 | |
307 | $this->persistEntity($row); |
308 | |
309 | return true; |
310 | } |
311 | |
312 | /** |
313 | * Activate a library card for the given username. |
314 | * |
315 | * @param UserEntityInterface|int $userOrId User owning card |
316 | * @param int $id Library card ID to activate |
317 | * |
318 | * @return void |
319 | * @throws \VuFind\Exception\LibraryCard |
320 | */ |
321 | public function activateLibraryCard(UserEntityInterface|int $userOrId, int $id): void |
322 | { |
323 | if (!$this->capabilities->libraryCardsEnabled()) { |
324 | throw new \VuFind\Exception\LibraryCard('Library Cards Disabled'); |
325 | } |
326 | $row = $this->getOrCreateLibraryCard($userOrId, $id); |
327 | $user = is_int($userOrId) |
328 | ? $this->getDbService(UserServiceInterface::class)->getUserById($userOrId) : $userOrId; |
329 | $user->setCatUsername($row->getCatUsername()); |
330 | $user->setRawCatPassword($row->getRawCatPassword()); |
331 | $user->setCatPassEnc($row->getCatPassEnc()); |
332 | $user->setHomeLibrary($row->getHomeLibrary()); |
333 | $this->persistEntity($user); |
334 | } |
335 | |
336 | /** |
337 | * Create a UserCard entity object. |
338 | * |
339 | * @return UserCardEntityInterface |
340 | */ |
341 | public function createEntity(): UserCardEntityInterface |
342 | { |
343 | return $this->getDbTable('UserCard')->createRow(); |
344 | } |
345 | } |