Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
33.33% covered (danger)
33.33%
13 / 39
35.71% covered (danger)
35.71%
5 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserService
33.33% covered (danger)
33.33%
13 / 39
35.71% covered (danger)
35.71%
5 / 14
243.00
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
 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     * Update the user's email address, if appropriate. Note that this does NOT
133     * automatically save the row; it assumes a subsequent call will be made to
134     * persist the data.
135     *
136     * @param UserEntityInterface $user         User entity to update
137     * @param string              $email        New email address
138     * @param bool                $userProvided Was this email provided by the user (true) or
139     * an automated lookup (false)?
140     *
141     * @return void
142     */
143    public function updateUserEmail(
144        UserEntityInterface $user,
145        string $email,
146        bool $userProvided = false
147    ): void {
148        // Only change the email if it is a non-empty value and was user provided
149        // (the user is always right) or the previous email was NOT user provided
150        // (a value may have changed in an upstream system).
151        if (!empty($email) && ($userProvided || !$user->hasUserProvidedEmail())) {
152            $user->setEmail($email);
153            $user->setHasUserProvidedEmail($userProvided);
154        }
155    }
156
157    /**
158     * Update session container to store data representing a user (used by privacy mode).
159     *
160     * @param UserEntityInterface $user User to store in session.
161     *
162     * @return void
163     * @throws Exception
164     */
165    public function addUserDataToSession(UserEntityInterface $user): void
166    {
167        if ($user instanceof UserRow) {
168            $this->userSessionContainer->userDetails = $user->toArray();
169        } else {
170            throw new \Exception($user::class . ' not supported by addUserDataToSession()');
171        }
172    }
173
174    /**
175     * Update session container to store user ID (used outside of privacy mode).
176     *
177     * @param int $id User ID
178     *
179     * @return void
180     */
181    public function addUserIdToSession(int $id): void
182    {
183        $this->userSessionContainer->userId = $id;
184    }
185
186    /**
187     * Clear the user data from the session.
188     *
189     * @return void
190     */
191    public function clearUserFromSession(): void
192    {
193        unset($this->userSessionContainer->userId);
194        unset($this->userSessionContainer->userDetails);
195    }
196
197    /**
198     * Build a user entity using data from a session container. Return null if user
199     * data cannot be found.
200     *
201     * @return ?UserEntityInterface
202     */
203    public function getUserFromSession(): ?UserEntityInterface
204    {
205        // If a user ID was persisted, that takes precedence:
206        if (isset($this->userSessionContainer->userId)) {
207            return $this->getUserById($this->userSessionContainer->userId);
208        }
209        if (isset($this->userSessionContainer->userDetails)) {
210            $user = $this->createEntity();
211            $user->exchangeArray($this->userSessionContainer->userDetails);
212            return $user;
213        }
214        return null;
215    }
216
217    /**
218     * Is there user data currently stored in the session container?
219     *
220     * @return bool
221     */
222    public function hasUserSessionData(): bool
223    {
224        return isset($this->userSessionContainer->userId)
225            || isset($this->userSessionContainer->userDetails);
226    }
227
228    /**
229     * Get all rows with catalog usernames.
230     *
231     * @return UserEntityInterface[]
232     */
233    public function getAllUsersWithCatUsernames(): array
234    {
235        $callback = function ($select) {
236            $select->where->isNotNull('cat_username');
237        };
238        return iterator_to_array($this->getDbTable('User')->select($callback));
239    }
240
241    /**
242     * Get user rows with insecure catalog passwords.
243     *
244     * @return UserEntityInterface[]
245     */
246    public function getInsecureRows(): array
247    {
248        return iterator_to_array($this->getDbTable('User')->getInsecureRows());
249    }
250
251    /**
252     * Create a new user entity.
253     *
254     * @return UserEntityInterface
255     */
256    public function createEntity(): UserEntityInterface
257    {
258        return $this->getDbTable('User')->createRow();
259    }
260}