Passed
Pull Request — master (#3688)
by
unknown
05:46
created

UserService   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 396
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 173
c 1
b 0
f 0
dl 0
loc 396
rs 10
wmc 21

19 Methods

Rating   Name   Duplication   Size   Complexity  
A findByIndividual() 0 10 1
A sortByLastLogin() 0 9 1
A filterInactive() 0 7 1
A findByEmail() 0 7 1
A find() 0 8 1
A findByIdentifier() 0 8 1
A findByToken() 0 13 1
A findByUserName() 0 7 1
A all() 0 7 1
A administrators() 0 11 1
A moderators() 0 12 1
A managers() 0 12 1
A contactLink() 0 26 3
A delete() 0 28 1
A create() 0 12 1
A unapproved() 0 18 1
A unverified() 0 18 1
A editors() 0 12 1
A allLoggedIn() 0 10 1
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2021 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Services;
21
22
use Closure;
23
use Fisharebest\Webtrees\Auth;
24
use Fisharebest\Webtrees\Carbon;
25
use Fisharebest\Webtrees\Contracts\UserInterface;
26
use Fisharebest\Webtrees\Registry;
27
use Fisharebest\Webtrees\Http\RequestHandlers\ContactPage;
28
use Fisharebest\Webtrees\Http\RequestHandlers\MessagePage;
29
use Fisharebest\Webtrees\Individual;
30
use Fisharebest\Webtrees\Tree;
31
use Fisharebest\Webtrees\User;
32
use Illuminate\Database\Capsule\Manager as DB;
33
use Illuminate\Database\Query\Builder;
34
use Illuminate\Database\Query\JoinClause;
35
use Illuminate\Support\Collection;
36
use Psr\Http\Message\ServerRequestInterface;
37
38
use function assert;
39
use function max;
40
41
/**
42
 * Functions for managing users.
43
 */
44
class UserService
45
{
46
    /**
47
     * Find the user with a specified user_id.
48
     *
49
     * @param int|null $user_id
50
     *
51
     * @return User|null
52
     */
53
    public function find($user_id): ?User
54
    {
55
        return Registry::cache()->array()->remember('user-' . $user_id, static function () use ($user_id): ?User {
56
            return DB::table('user')
57
                ->where('user_id', '=', $user_id)
58
                ->get()
59
                ->map(User::rowMapper())
60
                ->first();
61
        });
62
    }
63
64
    /**
65
     * Find the user with a specified email address.
66
     *
67
     * @param string $email
68
     *
69
     * @return User|null
70
     */
71
    public function findByEmail($email): ?User
72
    {
73
        return DB::table('user')
74
            ->where('email', '=', $email)
75
            ->get()
76
            ->map(User::rowMapper())
77
            ->first();
78
    }
79
80
    /**
81
     * Find the user with a specified user_name or email address.
82
     *
83
     * @param string $identifier
84
     *
85
     * @return User|null
86
     */
87
    public function findByIdentifier($identifier): ?User
88
    {
89
        return DB::table('user')
90
            ->where('user_name', '=', $identifier)
91
            ->orWhere('email', '=', $identifier)
92
            ->get()
93
            ->map(User::rowMapper())
94
            ->first();
95
    }
96
97
    /**
98
     * Find the user(s) with a specified genealogy record.
99
     *
100
     * @param Individual $individual
101
     *
102
     * @return Collection<User>
103
     */
104
    public function findByIndividual(Individual $individual): Collection
105
    {
106
        return DB::table('user')
107
            ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id')
108
            ->where('gedcom_id', '=', $individual->tree()->id())
109
            ->where('setting_value', '=', $individual->xref())
110
            ->where('setting_name', '=', UserInterface::PREF_TREE_ACCOUNT_XREF)
111
            ->select(['user.*'])
112
            ->get()
113
            ->map(User::rowMapper());
114
    }
115
116
    /**
117
     * Find the user with a specified password reset token.
118
     *
119
     * @param string $token
120
     *
121
     * @return User|null
122
     */
123
    public function findByToken(string $token): ?User
124
    {
125
        return DB::table('user')
126
            ->join('user_setting AS us1', 'us1.user_id', '=', 'user.user_id')
127
            ->where('us1.setting_name', '=', 'password-token')
128
            ->where('us1.setting_value', '=', $token)
129
            ->join('user_setting AS us2', 'us2.user_id', '=', 'user.user_id')
130
            ->where('us2.setting_name', '=', 'password-token-expire')
131
            ->where('us2.setting_value', '>', Carbon::now()->getTimestamp())
132
            ->select(['user.*'])
133
            ->get()
134
            ->map(User::rowMapper())
135
            ->first();
136
    }
137
138
    /**
139
     * Find the user with a specified user_name.
140
     *
141
     * @param string $user_name
142
     *
143
     * @return User|null
144
     */
145
    public function findByUserName($user_name): ?User
146
    {
147
        return DB::table('user')
148
            ->where('user_name', '=', $user_name)
149
            ->get()
150
            ->map(User::rowMapper())
151
            ->first();
152
    }
153
154
    /**
155
     * Callback to sort users by their last-login (or registration) time.
156
     *
157
     * @return Closure
158
     */
159
    public function sortByLastLogin(): Closure
160
    {
161
        return static function (UserInterface $user1, UserInterface $user2) {
162
            $registered_at1 = (int) $user1->getPreference(UserInterface::PREF_TIMESTAMP_REGISTERED);
163
            $logged_in_at1  = (int) $user1->getPreference(UserInterface::PREF_TIMESTAMP_ACTIVE);
164
            $registered_at2 = (int) $user2->getPreference(UserInterface::PREF_TIMESTAMP_REGISTERED);
165
            $logged_in_at2  = (int) $user2->getPreference(UserInterface::PREF_TIMESTAMP_ACTIVE);
166
167
            return max($registered_at1, $logged_in_at1) <=> max($registered_at2, $logged_in_at2);
168
        };
169
    }
170
171
    /**
172
     * Callback to filter users who have not logged in since a given time.
173
     *
174
     * @param int $timestamp
175
     *
176
     * @return Closure
177
     */
178
    public function filterInactive(int $timestamp): Closure
179
    {
180
        return static function (UserInterface $user) use ($timestamp): bool {
181
            $registered_at = (int) $user->getPreference(UserInterface::PREF_TIMESTAMP_REGISTERED);
182
            $logged_in_at  = (int) $user->getPreference(UserInterface::PREF_TIMESTAMP_ACTIVE);
183
184
            return max($registered_at, $logged_in_at) < $timestamp;
185
        };
186
    }
187
188
    /**
189
     * Get a list of all users.
190
     *
191
     * @return Collection<User>
192
     */
193
    public function all(): Collection
194
    {
195
        return DB::table('user')
196
            ->where('user_id', '>', 0)
197
            ->orderBy('real_name')
198
            ->get()
199
            ->map(User::rowMapper());
200
    }
201
202
    /**
203
     * Get a list of all administrators.
204
     *
205
     * @return Collection<User>
206
     */
207
    public function administrators(): Collection
208
    {
209
        return DB::table('user')
210
            ->join('user_setting', 'user_setting.user_id', '=', 'user.user_id')
211
            ->where('user_setting.setting_name', '=', UserInterface::PREF_IS_ADMINISTRATOR)
212
            ->where('user_setting.setting_value', '=', '1')
213
            ->where('user.user_id', '>', 0)
214
            ->orderBy('real_name')
215
            ->select(['user.*'])
216
            ->get()
217
            ->map(User::rowMapper());
218
    }
219
220
    /**
221
     * Get a list of all managers.
222
     *
223
     * @return Collection<User>
224
     */
225
    public function managers(): Collection
226
    {
227
        return DB::table('user')
228
            ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id')
229
            ->where('user_gedcom_setting.setting_name', '=', UserInterface::PREF_TREE_ROLE)
230
            ->where('user_gedcom_setting.setting_value', '=', UserInterface::ROLE_MANAGER)
231
            ->where('user.user_id', '>', 0)
232
            ->groupBy(['user.user_id'])
233
            ->orderBy('real_name')
234
            ->select(['user.*'])
235
            ->get()
236
            ->map(User::rowMapper());
237
    }
238
239
    /**
240
     * Get a list of all moderators.
241
     *
242
     * @return Collection<User>
243
     */
244
    public function moderators(): Collection
245
    {
246
        return DB::table('user')
247
            ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id')
248
            ->where('user_gedcom_setting.setting_name', '=', UserInterface::PREF_TREE_ROLE)
249
            ->where('user_gedcom_setting.setting_value', '=', UserInterface::ROLE_MODERATOR)
250
            ->where('user.user_id', '>', 0)
251
            ->groupBy(['user.user_id'])
252
            ->orderBy('real_name')
253
            ->select(['user.*'])
254
            ->get()
255
            ->map(User::rowMapper());
256
    }
257
258
    /**
259
     * Get a list of all editors.
260
     *
261
     * @return Collection<User>
262
     */
263
264
    public function editors(): Collection
265
    {
266
        return DB::table('user')
267
            ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id')
268
            ->where('user_gedcom_setting.setting_name', '=', User::PREF_TREE_ROLE)
269
            ->where('user_gedcom_setting.setting_value', '=', User::ROLE_EDITOR)
270
            ->where('user.user_id', '>', 0)
271
            ->groupBy(['user.user_id'])
272
            ->orderBy('real_name')
273
            ->select(['user.*'])
274
            ->get()
275
            ->map(User::rowMapper());
276
    }
277
278
    /**
279
     * Get a list of all verified users.
280
     *
281
     * @return Collection<User>
282
     */
283
    public function unapproved(): Collection
284
    {
285
        return DB::table('user')
286
            ->leftJoin('user_setting', static function (JoinClause $join): void {
287
                $join
288
                    ->on('user_setting.user_id', '=', 'user.user_id')
289
                    ->where('user_setting.setting_name', '=', UserInterface::PREF_IS_ACCOUNT_APPROVED);
290
            })
291
            ->where(static function (Builder $query): void {
292
                $query
293
                    ->where('user_setting.setting_value', '<>', '1')
294
                    ->orWhereNull('user_setting.setting_value');
295
            })
296
            ->where('user.user_id', '>', 0)
297
            ->orderBy('real_name')
298
            ->select(['user.*'])
299
            ->get()
300
            ->map(User::rowMapper());
301
    }
302
303
    /**
304
     * Get a list of all verified users.
305
     *
306
     * @return Collection<User>
307
     */
308
    public function unverified(): Collection
309
    {
310
        return DB::table('user')
311
            ->leftJoin('user_setting', static function (JoinClause $join): void {
312
                $join
313
                    ->on('user_setting.user_id', '=', 'user.user_id')
314
                    ->where('user_setting.setting_name', '=', UserInterface::PREF_IS_EMAIL_VERIFIED);
315
            })
316
            ->where(static function (Builder $query): void {
317
                $query
318
                    ->where('user_setting.setting_value', '<>', '1')
319
                    ->orWhereNull('user_setting.setting_value');
320
            })
321
            ->where('user.user_id', '>', 0)
322
            ->orderBy('real_name')
323
            ->select(['user.*'])
324
            ->get()
325
            ->map(User::rowMapper());
326
    }
327
328
    /**
329
     * Get a list of all users who are currently logged in.
330
     *
331
     * @return Collection<User>
332
     */
333
    public function allLoggedIn(): Collection
334
    {
335
        return DB::table('user')
336
            ->join('session', 'session.user_id', '=', 'user.user_id')
337
            ->where('user.user_id', '>', 0)
338
            ->orderBy('real_name')
339
            ->select(['user.*'])
340
            ->distinct()
341
            ->get()
342
            ->map(User::rowMapper());
343
    }
344
345
    /**
346
     * Create a new user.
347
     * The calling code needs to check for duplicates identifiers before calling
348
     * this function.
349
     *
350
     * @param string $user_name
351
     * @param string $real_name
352
     * @param string $email
353
     * @param string $password
354
     *
355
     * @return User
356
     */
357
    public function create(string $user_name, string $real_name, string $email, string $password): User
358
    {
359
        DB::table('user')->insert([
360
            'user_name' => $user_name,
361
            'real_name' => $real_name,
362
            'email'     => $email,
363
            'password'  => password_hash($password, PASSWORD_DEFAULT),
364
        ]);
365
366
        $user_id = (int) DB::connection()->getPdo()->lastInsertId();
367
368
        return new User($user_id, $user_name, $real_name, $email);
369
    }
370
371
    /**
372
     * Delete a user
373
     *
374
     * @param User $user
375
     *
376
     * @return void
377
     */
378
    public function delete(User $user): void
379
    {
380
        // Don't delete the logs, just set the user to null.
381
        DB::table('log')
382
            ->where('user_id', '=', $user->id())
383
            ->update(['user_id' => null]);
384
385
        // Take over the user’s pending changes. (What else could we do with them?)
386
        DB::table('change')
387
            ->where('user_id', '=', $user->id())
388
            ->where('status', '=', 'rejected')
389
            ->delete();
390
391
        DB::table('change')
392
            ->where('user_id', '=', $user->id())
393
            ->update(['user_id' => Auth::id()]);
394
395
        // Delete settings and preferences
396
        DB::table('block_setting')
397
            ->join('block', 'block_setting.block_id', '=', 'block.block_id')
398
            ->where('user_id', '=', $user->id())
399
            ->delete();
400
401
        DB::table('block')->where('user_id', '=', $user->id())->delete();
402
        DB::table('user_gedcom_setting')->where('user_id', '=', $user->id())->delete();
403
        DB::table('user_setting')->where('user_id', '=', $user->id())->delete();
404
        DB::table('message')->where('user_id', '=', $user->id())->delete();
405
        DB::table('user')->where('user_id', '=', $user->id())->delete();
406
    }
407
408
    /**
409
     * @param User                   $contact_user
410
     * @param ServerRequestInterface $request
411
     *
412
     * @return string
413
     */
414
    public function contactLink(User $contact_user, ServerRequestInterface $request): string
415
    {
416
        $tree = $request->getAttribute('tree');
417
        assert($tree instanceof Tree);
418
419
        $user = $request->getAttribute('user');
420
421
        if ($contact_user->getPreference(UserInterface::PREF_CONTACT_METHOD) === 'mailto') {
422
            $url = 'mailto:' . $contact_user->email();
423
        } elseif ($user instanceof User) {
424
            // Logged-in users send direct messages
425
            $url = route(MessagePage::class, [
426
                'to' => $contact_user->userName(),
427
                'tree' => $tree->name(),
428
                'url'  => (string) $request->getUri(),
429
            ]);
430
        } else {
431
            // Visitors use the contact form.
432
            $url = route(ContactPage::class, [
433
                'to'   => $contact_user->userName(),
434
                'tree' => $tree->name(),
435
                'url'  => (string) $request->getUri(),
436
            ]);
437
        }
438
439
        return '<a href="' . e($url) . '" dir="auto">' . e($contact_user->realName()) . '</a>';
440
    }
441
}
442