Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
34.88% covered (danger)
34.88%
15 / 43
38.89% covered (danger)
38.89%
7 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserService
34.88% covered (danger)
34.88%
15 / 43
38.89% covered (danger)
38.89%
7 / 18
296.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createEntityForUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deleteUser
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getUserById
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserByField
45.45% covered (danger)
45.45%
5 / 11
0.00% covered (danger)
0.00%
0 / 1
11.84
 getUserByCatId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserByEmail
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserByUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserByVerifyHash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateUserEmail
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 addUserDataToSession
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 addUserIdToSession
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 clearUserFromSession
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getUserFromSession
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 hasUserSessionData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getAllUsersWithCatUsernames
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getInsecureRows
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createEntity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Database service for user.
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 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development:plugins:database_gateways Wiki
28 */
29
30namespace VuFind\Db\Service;
31
32use Laminas\Log\LoggerAwareInterface;
33use Laminas\Session\Container as SessionContainer;
34use VuFind\Auth\UserSessionPersistenceInterface;
35use VuFind\Db\Entity\UserEntityInterface;
36use VuFind\Db\Row\User as UserRow;
37use VuFind\Db\Table\DbTableAwareInterface;
38use VuFind\Db\Table\DbTableAwareTrait;
39use VuFind\Log\LoggerAwareTrait;
40
41/**
42 * Database service for user.
43 *
44 * @category VuFind
45 * @package  Database
46 * @author   Demian Katz <demian.katz@villanova.edu>
47 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
48 * @link     https://vufind.org/wiki/development:plugins:database_gateways Wiki
49 */
50class UserService extends AbstractDbService implements
51    DbTableAwareInterface,
52    LoggerAwareInterface,
53    UserServiceInterface,
54    UserSessionPersistenceInterface
55{
56    use DbTableAwareTrait;
57    use LoggerAwareTrait;
58
59    /**
60     * Constructor
61     *
62     * @param SessionContainer $userSessionContainer Session container for user data
63     */
64    public function __construct(protected SessionContainer $userSessionContainer)
65    {
66    }
67
68    /**
69     * Create an entity for the specified username.
70     *
71     * @param string $username Username
72     *
73     * @return UserEntityInterface
74     */
75    public function createEntityForUsername(string $username): UserEntityInterface
76    {
77        return $this->getDbTable('User')->createRowForUsername($username);
78    }
79
80    /**
81     * Delete a user entity.
82     *
83     * @param UserEntityInterface|int $userOrId User entity object or ID to delete
84     *
85     * @return void
86     */
87    public function deleteUser(UserEntityInterface|int $userOrId): void
88    {
89        $userId = $userOrId instanceof UserEntityInterface ? $userOrId->getId() : $userOrId;
90        $this->getDbTable('User')->delete(['id' => $userId]);
91    }
92
93    /**
94     * Retrieve a user object from the database based on ID.
95     *
96     * @param int $id ID.
97     *
98     * @return ?UserEntityInterface
99     */
100    public function getUserById(int $id): ?UserEntityInterface
101    {
102        return $this->getDbTable('User')->getById($id);
103    }
104
105    /**
106     * Retrieve a user object from the database based on the given field.
107     * Field name must be id, username, email, verify_hash or cat_id.
108     *
109     * @param string          $fieldName  Field name
110     * @param int|string|null $fieldValue Field value
111     *
112     * @return ?UserEntityInterface
113     */
114    public function getUserByField(string $fieldName, int|string|null $fieldValue): ?UserEntityInterface
115    {
116        switch ($fieldName) {
117            case 'email':
118                return $this->getDbTable('User')->getByEmail($fieldValue);
119            case 'id':
120                return $this->getDbTable('User')->getById($fieldValue);
121            case 'username':
122                return $this->getDbTable('User')->getByUsername($fieldValue, false);
123            case 'verify_hash':
124                return $this->getDbTable('User')->getByVerifyHash($fieldValue);
125            case 'cat_id':
126                return $this->getDbTable('User')->getByCatalogId($fieldValue);
127        }
128        throw new \InvalidArgumentException('Field name must be id, username, email or cat_id');
129    }
130
131    /**
132     * Retrieve a user object by catalog ID. Returns null if no match is found.
133     *
134     * @param string $catId Catalog ID
135     *
136     * @return ?UserEntityInterface
137     */
138    public function getUserByCatId(string $catId): ?UserEntityInterface
139    {
140        return $this->getUserByField('cat_id', $catId);
141    }
142
143    /**
144     * Retrieve a user object by email address. Returns null if no match is found.
145     *
146     * @param string $email Email address
147     *
148     * @return ?UserEntityInterface
149     */
150    public function getUserByEmail(string $email): ?UserEntityInterface
151    {
152        return $this->getUserByField('email', $email);
153    }
154
155    /**
156     * Retrieve a user object by username. Returns null if no match is found.
157     *
158     * @param string $username Username
159     *
160     * @return ?UserEntityInterface
161     */
162    public function getUserByUsername(string $username): ?UserEntityInterface
163    {
164        return $this->getUserByField('username', $username);
165    }
166
167    /**
168     * Retrieve a user object by verify hash. Returns null if no match is found.
169     *
170     * @param string $hash Verify hash
171     *
172     * @return ?UserEntityInterface
173     */
174    public function getUserByVerifyHash(string $hash): ?UserEntityInterface
175    {
176        return $this->getUserByField('verify_hash', $hash);
177    }
178
179    /**
180     * Update the user's email address, if appropriate. Note that this does NOT
181     * automatically save the row; it assumes a subsequent call will be made to
182     * persist the data.
183     *
184     * @param UserEntityInterface $user         User entity to update
185     * @param string              $email        New email address
186     * @param bool                $userProvided Was this email provided by the user (true) or
187     * an automated lookup (false)?
188     *
189     * @return void
190     */
191    public function updateUserEmail(
192        UserEntityInterface $user,
193        string $email,
194        bool $userProvided = false
195    ): void {
196        // Only change the email if it is a non-empty value and was user provided
197        // (the user is always right) or the previous email was NOT user provided
198        // (a value may have changed in an upstream system).
199        if (!empty($email) && ($userProvided || !$user->hasUserProvidedEmail())) {
200            $user->setEmail($email);
201            $user->setHasUserProvidedEmail($userProvided);
202        }
203    }
204
205    /**
206     * Update session container to store data representing a user (used by privacy mode).
207     *
208     * @param UserEntityInterface $user User to store in session.
209     *
210     * @return void
211     * @throws Exception
212     */
213    public function addUserDataToSession(UserEntityInterface $user): void
214    {
215        if ($user instanceof UserRow) {
216            $this->userSessionContainer->userDetails = $user->toArray();
217        } else {
218            throw new \Exception($user::class . ' not supported by addUserDataToSession()');
219        }
220    }
221
222    /**
223     * Update session container to store user ID (used outside of privacy mode).
224     *
225     * @param int $id User ID
226     *
227     * @return void
228     */
229    public function addUserIdToSession(int $id): void
230    {
231        $this->userSessionContainer->userId = $id;
232    }
233
234    /**
235     * Clear the user data from the session.
236     *
237     * @return void
238     */
239    public function clearUserFromSession(): void
240    {
241        unset($this->userSessionContainer->userId);
242        unset($this->userSessionContainer->userDetails);
243    }
244
245    /**
246     * Build a user entity using data from a session container. Return null if user
247     * data cannot be found.
248     *
249     * @return ?UserEntityInterface
250     */
251    public function getUserFromSession(): ?UserEntityInterface
252    {
253        // If a user ID was persisted, that takes precedence:
254        if (isset($this->userSessionContainer->userId)) {
255            return $this->getUserById($this->userSessionContainer->userId);
256        }
257        if (isset($this->userSessionContainer->userDetails)) {
258            $user = $this->createEntity();
259            $user->exchangeArray($this->userSessionContainer->userDetails);
260            return $user;
261        }
262        return null;
263    }
264
265    /**
266     * Is there user data currently stored in the session container?
267     *
268     * @return bool
269     */
270    public function hasUserSessionData(): bool
271    {
272        return isset($this->userSessionContainer->userId)
273            || isset($this->userSessionContainer->userDetails);
274    }
275
276    /**
277     * Get all rows with catalog usernames.
278     *
279     * @return UserEntityInterface[]
280     */
281    public function getAllUsersWithCatUsernames(): array
282    {
283        $callback = function ($select) {
284            $select->where->isNotNull('cat_username');
285        };
286        return iterator_to_array($this->getDbTable('User')->select($callback));
287    }
288
289    /**
290     * Get user rows with insecure catalog passwords.
291     *
292     * @return UserEntityInterface[]
293     */
294    public function getInsecureRows(): array
295    {
296        return iterator_to_array($this->getDbTable('User')->getInsecureRows());
297    }
298
299    /**
300     * Create a new user entity.
301     *
302     * @return UserEntityInterface
303     */
304    public function createEntity(): UserEntityInterface
305    {
306        return $this->getDbTable('User')->createRow();
307    }
308}