Passed
Push — develop ( c341e4...5dd870 )
by Greg
05:58
created

UsersController::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 12
rs 9.9666
c 0
b 0
f 0
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\Http\Controllers\Admin;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Carbon;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Carbon was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use Fisharebest\Webtrees\Contracts\UserInterface;
25
use Fisharebest\Webtrees\Exceptions\HttpNotFoundException;
26
use Fisharebest\Webtrees\FlashMessages;
27
use Fisharebest\Webtrees\I18N;
28
use Fisharebest\Webtrees\Log;
29
use Fisharebest\Webtrees\Module\ModuleLanguageInterface;
30
use Fisharebest\Webtrees\Module\ModuleThemeInterface;
31
use Fisharebest\Webtrees\Services\DatatablesService;
32
use Fisharebest\Webtrees\Services\EmailService;
33
use Fisharebest\Webtrees\Services\MessageService;
34
use Fisharebest\Webtrees\Services\ModuleService;
35
use Fisharebest\Webtrees\Services\TreeService;
36
use Fisharebest\Webtrees\Services\UserService;
37
use Fisharebest\Webtrees\Site;
38
use Fisharebest\Webtrees\SiteUser;
39
use Fisharebest\Webtrees\User;
40
use Illuminate\Database\Capsule\Manager as DB;
41
use Illuminate\Database\Query\JoinClause;
42
use Psr\Http\Message\ResponseInterface;
43
use Psr\Http\Message\ServerRequestInterface;
44
use stdClass;
45
46
use function e;
47
use function route;
48
49
/**
50
 * Controller for user administration.
51
 */
52
class UsersController extends AbstractAdminController
53
{
54
    /** @var DatatablesService */
55
    private $datatables_service;
56
57
    /** @var EmailService */
58
    private $email_service;
59
60
    /** @var MessageService */
61
    private $message_service;
62
63
    /** @var ModuleService */
64
    private $module_service;
65
66
    /** @var UserService */
67
    private $user_service;
68
69
    /** @var TreeService */
70
    private $tree_service;
71
72
    /**
73
     * UsersController constructor.
74
     *
75
     * @param DatatablesService $datatables_service
76
     * @param EmailService      $email_service
77
     * @param MessageService    $message_service
78
     * @param ModuleService     $module_service
79
     * @param TreeService       $tree_service
80
     * @param UserService       $user_service
81
     */
82
    public function __construct(
83
        DatatablesService $datatables_service,
84
        EmailService $email_service,
85
        MessageService $message_service,
86
        ModuleService $module_service,
87
        TreeService $tree_service,
88
        UserService $user_service
89
    ) {
90
        $this->datatables_service = $datatables_service;
91
        $this->email_service      = $email_service;
92
        $this->message_service    = $message_service;
93
        $this->module_service     = $module_service;
94
        $this->tree_service       = $tree_service;
95
        $this->user_service       = $user_service;
96
    }
97
98
    /**
99
     * @param ServerRequestInterface $request
100
     *
101
     * @return ResponseInterface
102
     */
103
    public function index(ServerRequestInterface $request): ResponseInterface
104
    {
105
        $user = $request->getAttribute('user');
106
107
        $filter = $request->getQueryParams()['filter'] ?? '';
108
109
        $all_users = $this->user_service->all();
110
111
        $page_size = (int) $user->getPreference(' admin_users_page_size', '10');
112
113
        $title = I18N::translate('User administration');
114
115
        return $this->viewResponse('admin/users', [
116
            'all_users' => $all_users,
117
            'filter'    => $filter,
118
            'page_size' => $page_size,
119
            'title'     => $title,
120
        ]);
121
    }
122
123
    /**
124
     * @param ServerRequestInterface $request
125
     *
126
     * @return ResponseInterface
127
     */
128
    public function data(ServerRequestInterface $request): ResponseInterface
129
    {
130
        $user = $request->getAttribute('user');
131
132
        $languages = $this->module_service->findByInterface(ModuleLanguageInterface::class, true)
133
            ->mapWithKeys(static function (ModuleLanguageInterface $module): array {
134
                $locale = $module->locale();
135
136
                return [$locale->languageTag() => $locale->endonym()];
137
            });
138
139
        $query = DB::table('user')
140
            ->leftJoin('user_setting AS us1', static function (JoinClause $join): void {
141
                $join
142
                    ->on('us1.user_id', '=', 'user.user_id')
143
                    ->where('us1.setting_name', '=', 'language');
144
            })
145
            ->leftJoin('user_setting AS us2', static function (JoinClause $join): void {
146
                $join
147
                    ->on('us2.user_id', '=', 'user.user_id')
148
                    ->where('us2.setting_name', '=', UserInterface::PREF_TIMESTAMP_REGISTERED);
149
            })
150
            ->leftJoin('user_setting AS us3', static function (JoinClause $join): void {
151
                $join
152
                    ->on('us3.user_id', '=', 'user.user_id')
153
                    ->where('us3.setting_name', '=', UserInterface::PREF_TIMESTAMP_ACTIVE);
154
            })
155
            ->leftJoin('user_setting AS us4', static function (JoinClause $join): void {
156
                $join
157
                    ->on('us4.user_id', '=', 'user.user_id')
158
                    ->where('us4.setting_name', '=', UserInterface::PREF_IS_EMAIL_VERIFIED);
159
            })
160
            ->leftJoin('user_setting AS us5', static function (JoinClause $join): void {
161
                $join
162
                    ->on('us5.user_id', '=', 'user.user_id')
163
                    ->where('us5.setting_name', '=', UserInterface::PREF_IS_ACCOUNT_APPROVED);
164
            })
165
            ->where('user.user_id', '>', '0')
166
            ->select([
167
                'user.user_id AS edit_menu', // Hidden column
168
                'user.user_id',
169
                'user_name',
170
                'real_name',
171
                'email',
172
                'us1.setting_value AS language',
173
                'us2.setting_value AS registered_at_sort', // Hidden column
174
                'us2.setting_value AS registered_at',
175
                'us3.setting_value AS active_at_sort', // Hidden column
176
                'us3.setting_value AS active_at',
177
                'us4.setting_value AS verified',
178
                'us5.setting_value AS verified_by_admin',
179
            ]);
180
181
        $search_columns = ['user_name', 'real_name', 'email'];
182
        $sort_columns   = [];
183
184
        $callback = function (stdClass $row) use ($languages, $user): array {
185
            $row_user = $this->user_service->find($row->user_id);
186
            $datum = [
187
                view('admin/users-table-options', ['row' => $row, 'self' => $user, 'user' => $row_user]),
188
                $row->user_id,
189
                '<span dir="auto">' . e($row->user_name) . '</span>',
190
                '<span dir="auto">' . e($row->real_name) . '</span>',
191
                '<a href="mailto:' . e($row->email) . '">' . e($row->email) . '</a>',
192
                $languages->get($row->language, $row->language),
193
                $row->registered_at,
194
                $row->registered_at ? view('components/datetime-diff', ['timestamp' => Carbon::createFromTimestamp((int) $row->registered_at)]) : '',
195
                $row->active_at,
196
                $row->active_at ? view('components/datetime-diff', ['timestamp' => Carbon::createFromTimestamp((int) $row->active_at)]) : I18N::translate('Never'),
197
                $row->verified ? I18N::translate('yes') : I18N::translate('no'),
198
                $row->verified_by_admin ? I18N::translate('yes') : I18N::translate('no'),
199
            ];
200
201
            // Highlight old registrations.
202
            if (!$datum[10] && date('U') - $datum[6] > 604800) {
203
                $datum[7] = '<span class="text-danger">' . $datum[7] . '</span>';
204
            }
205
206
            return $datum;
207
        };
208
209
        return $this->datatables_service->handleQuery($request, $query, $search_columns, $sort_columns, $callback);
210
    }
211
212
    /**
213
     * @param ServerRequestInterface $request
214
     *
215
     * @return ResponseInterface
216
     */
217
    public function create(ServerRequestInterface $request): ResponseInterface
218
    {
219
        $email     = $request->getQueryParams()['email'] ?? '';
220
        $real_name = $request->getQueryParams()['real_name'] ?? '';
221
        $username  = $request->getQueryParams()['username'] ?? '';
222
        $title     = I18N::translate('Add a user');
223
224
        return $this->viewResponse('admin/users-create', [
225
            'email'     => $email,
226
            'real_name' => $real_name,
227
            'title'     => $title,
228
            'username'  => $username,
229
        ]);
230
    }
231
232
    /**
233
     * @param ServerRequestInterface $request
234
     *
235
     * @return ResponseInterface
236
     */
237
    public function edit(ServerRequestInterface $request): ResponseInterface
238
    {
239
        $user_id = (int) $request->getQueryParams()['user_id'];
240
        $user    = $this->user_service->find($user_id);
241
242
        if ($user === null) {
243
            throw new HttpNotFoundException(I18N::translate('%1$s does not exist.', 'user_id:' . $user_id));
244
        }
245
246
        $languages = $this->module_service->findByInterface(ModuleLanguageInterface::class, true, true)
247
            ->mapWithKeys(static function (ModuleLanguageInterface $module): array {
248
                $locale = $module->locale();
249
250
                return [$locale->languageTag() => $locale->endonym()];
251
            });
252
253
        $roles = [
254
            /* I18N: Listbox entry; name of a role */
255
            UserInterface::ROLE_VISITOR   => I18N::translate('Visitor'),
256
            /* I18N: Listbox entry; name of a role */
257
            UserInterface::ROLE_MEMBER => I18N::translate('Member'),
258
            /* I18N: Listbox entry; name of a role */
259
            UserInterface::ROLE_EDITOR   => I18N::translate('Editor'),
260
            /* I18N: Listbox entry; name of a role */
261
            UserInterface::ROLE_MODERATOR => I18N::translate('Moderator'),
262
            /* I18N: Listbox entry; name of a role */
263
            UserInterface::ROLE_MANAGER  => I18N::translate('Manager'),
264
        ];
265
266
        $theme_options = $this->module_service
267
            ->findByInterface(ModuleThemeInterface::class)
268
            ->map($this->module_service->titleMapper())
269
            ->prepend(I18N::translate('<default theme>'), '');
270
271
        return $this->viewResponse('admin/users-edit', [
272
            'contact_methods'  => $this->message_service->contactMethods(),
273
            'default_language' => I18N::languageTag(),
274
            'languages'        => $languages->all(),
275
            'roles'            => $roles,
276
            'trees'            => $this->tree_service->all(),
277
            'theme_options'    => $theme_options,
278
            'title'            => I18N::translate('Edit the user'),
279
            'user'             => $user,
280
        ]);
281
    }
282
283
    /**
284
     * @param ServerRequestInterface $request
285
     *
286
     * @return ResponseInterface
287
     */
288
    public function save(ServerRequestInterface $request): ResponseInterface
289
    {
290
        $params = (array) $request->getParsedBody();
291
292
        $username  = $params['username'];
293
        $real_name = $params['real_name'];
294
        $email     = $params['email'];
295
        $password  = $params['password'];
296
297
        $errors = false;
298
        if ($this->user_service->findByUserName($username)) {
299
            FlashMessages::addMessage(I18N::translate('Duplicate username. A user with that username already exists. Please choose another username.'));
300
            $errors = true;
301
        }
302
303
        if ($this->user_service->findByEmail($email)) {
304
            FlashMessages::addMessage(I18N::translate('Duplicate email address. A user with that email already exists.'));
305
            $errors = true;
306
        }
307
308
        if ($errors) {
309
            $url = route('admin-users-create', [
310
                'email'     => $email,
311
                'real_name' => $real_name,
312
                'username'  => $username,
313
            ]);
314
315
            return redirect($url);
316
        }
317
318
        $new_user = $this->user_service->create($username, $real_name, $email, $password);
319
        $new_user->setPreference(UserInterface::PREF_IS_EMAIL_VERIFIED, '1');
320
        $new_user->setPreference(UserInterface::PREF_LANGUAGE, I18N::languageTag());
321
        $new_user->setPreference(UserInterface::PREF_TIME_ZONE, Site::getPreference('TIMEZONE', 'UTC'));
322
        $new_user->setPreference(UserInterface::PREF_TIMESTAMP_REGISTERED, date('U'));
323
        $new_user->setPreference(UserInterface::PREF_TIMESTAMP_ACTIVE, '0');
324
325
        Log::addAuthenticationLog('User ->' . $username . '<- created');
326
327
        $url = route('admin-users-edit', [
328
            'user_id' => $new_user->id(),
329
        ]);
330
331
        return redirect($url);
332
    }
333
334
    /**
335
     * @param ServerRequestInterface $request
336
     *
337
     * @return ResponseInterface
338
     */
339
    public function update(ServerRequestInterface $request): ResponseInterface
340
    {
341
        $user = $request->getAttribute('user');
342
343
        $params = (array) $request->getParsedBody();
344
345
        $user_id        = (int) $params['user_id'];
346
        $username       = $params['username'];
347
        $real_name      = $params['real_name'];
348
        $email          = $params['email'];
349
        $password       = $params['password'];
350
        $theme          = $params['theme'];
351
        $language       = $params['language'];
352
        $timezone       = $params['timezone'];
353
        $contact_method = $params['contact-method'];
354
        $comment        = $params['comment'];
355
        $auto_accept    = (bool) ($params[UserInterface::PREF_AUTO_ACCEPT_EDITS] ?? '');
356
        $canadmin       = (bool) ($params[UserInterface::PREF_IS_ADMINISTRATOR] ?? '');
357
        $visible_online = (bool) ($params['visible-online'] ?? '');
358
        $verified       = (bool) ($params[UserInterface::PREF_IS_EMAIL_VERIFIED] ?? '');
359
        $approved       = (bool) ($params['approved'] ?? '');
360
361
        $edit_user = $this->user_service->find($user_id);
362
363
        if ($edit_user === null) {
364
            throw new HttpNotFoundException(I18N::translate('%1$s does not exist', 'user_id:' . $user_id));
365
        }
366
367
        // We have just approved a user.  Tell them
368
        if ($approved && $edit_user->getPreference(UserInterface::PREF_IS_ACCOUNT_APPROVED) !== '1') {
369
            I18N::init($edit_user->getPreference(UserInterface::PREF_LANGUAGE));
370
371
            $base_url = $request->getAttribute('base_url');
372
373
            $this->email_service->send(
374
                new SiteUser(),
375
                $edit_user,
376
                Auth::user(),
377
                /* I18N: %s is a server name/URL */
378
                I18N::translate('New user at %s', $base_url),
379
                view('emails/approve-user-text', ['user' => $edit_user, 'base_url' => $base_url]),
380
                view('emails/approve-user-html', ['user' => $edit_user, 'base_url' => $base_url])
381
            );
382
        }
383
384
        $edit_user->setRealName($real_name);
385
        $edit_user->setPreference(UserInterface::PREF_THEME, $theme);
386
        $edit_user->setPreference(UserInterface::PREF_LANGUAGE, $language);
387
        $edit_user->setPreference(UserInterface::PREF_TIME_ZONE, $timezone);
388
        $edit_user->setPreference(UserInterface::PREF_CONTACT_METHOD, $contact_method);
389
        $edit_user->setPreference(UserInterface::PREF_NEW_ACCOUNT_COMMENT, $comment);
390
        $edit_user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, (string) $auto_accept);
391
        $edit_user->setPreference(UserInterface::PREF_IS_VISIBLE_ONLINE, (string) $visible_online);
392
        $edit_user->setPreference(UserInterface::PREF_IS_EMAIL_VERIFIED, (string) $verified);
393
        $edit_user->setPreference(UserInterface::PREF_IS_ACCOUNT_APPROVED, (string) $approved);
394
395
        if ($password !== '') {
396
            $edit_user->setPassword($password);
397
        }
398
399
        // We cannot change our own admin status. Another admin will need to do it.
400
        if ($edit_user->id() !== $user->id()) {
401
            $edit_user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, $canadmin ? '1' : '');
402
        }
403
404
        foreach ($this->tree_service->all() as $tree) {
405
            $path_length = (int) $params['RELATIONSHIP_PATH_LENGTH' . $tree->id()];
406
            $gedcom_id   = $params['gedcomid' . $tree->id()] ?? '';
407
            $can_edit    = $params['canedit' . $tree->id()] ?? '';
408
409
            // Do not allow a path length to be set if the individual ID is not
410
            if ($gedcom_id === '') {
411
                $path_length = 0;
412
            }
413
414
            $tree->setUserPreference($edit_user, UserInterface::PREF_TREE_ACCOUNT_XREF, $gedcom_id);
415
            $tree->setUserPreference($edit_user, UserInterface::PREF_TREE_ROLE, $can_edit);
416
            $tree->setUserPreference($edit_user, UserInterface::PREF_TREE_PATH_LENGTH, (string) $path_length);
417
        }
418
419
        if ($edit_user->email() !== $email && $this->user_service->findByEmail($email) instanceof User) {
420
            FlashMessages::addMessage(I18N::translate('Duplicate email address. A user with that email already exists.') . $email, 'danger');
421
422
            return redirect(route('admin-users-edit', ['user_id' => $edit_user->id()]));
423
        }
424
425
        if ($edit_user->userName() !== $username && $this->user_service->findByUserName($username) instanceof User) {
426
            FlashMessages::addMessage(I18N::translate('Duplicate username. A user with that username already exists. Please choose another username.'), 'danger');
427
428
            return redirect(route('admin-users-edit', ['user_id' => $edit_user->id()]));
429
        }
430
431
        $edit_user
432
            ->setEmail($email)
433
            ->setUserName($username);
434
435
        return redirect(route('admin-users'));
436
    }
437
}
438