Passed
Push — master ( f2de56...1dcac8 )
by Greg
05:22
created

AdminSiteController::serverInformation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 11
rs 10
1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2019 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
 * GNU General Public License for more details.
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
declare(strict_types=1);
17
18
namespace Fisharebest\Webtrees\Http\Controllers;
19
20
use Exception;
21
use Fig\Http\Message\StatusCodeInterface;
22
use Fisharebest\Webtrees\Carbon;
23
use Fisharebest\Webtrees\File;
24
use Fisharebest\Webtrees\FlashMessages;
25
use Fisharebest\Webtrees\I18N;
26
use Fisharebest\Webtrees\Module\ModuleThemeInterface;
27
use Fisharebest\Webtrees\Services\DatatablesService;
28
use Fisharebest\Webtrees\Services\MailService;
29
use Fisharebest\Webtrees\Services\ModuleService;
30
use Fisharebest\Webtrees\Services\UserService;
31
use Fisharebest\Webtrees\Site;
32
use Fisharebest\Webtrees\Tree;
33
use Illuminate\Database\Capsule\Manager as DB;
34
use Illuminate\Database\Query\Builder;
35
use Illuminate\Database\Query\Expression;
36
use Illuminate\Support\Collection;
37
use League\Flysystem\FilesystemInterface;
38
use Psr\Http\Message\ResponseInterface;
39
use Psr\Http\Message\ServerRequestInterface;
40
use stdClass;
41
use function filter_var;
42
use const FILTER_VALIDATE_DOMAIN;
43
44
/**
45
 * Controller for site administration.
46
 */
47
class AdminSiteController extends AbstractBaseController
48
{
49
    /** @var string */
50
    protected $layout = 'layouts/administration';
51
52
    /**
53
     * @var ModuleService
54
     */
55
    private $module_service;
56
57
    /**
58
     * @var UserService
59
     */
60
    private $user_service;
61
62
    /**
63
     * AdminSiteController constructor.
64
     *
65
     * @param ModuleService $module_service
66
     * @param UserService   $user_service
67
     */
68
    public function __construct(ModuleService $module_service, UserService $user_service)
69
    {
70
        $this->module_service = $module_service;
71
        $this->user_service   = $user_service;
72
    }
73
74
    /**
75
     * Show old user files in the data folder.
76
     *
77
     * @param ServerRequestInterface $request
78
     * @param FilesystemInterface    $filesystem
79
     *
80
     * @return ResponseInterface
81
     */
82
    public function cleanData(ServerRequestInterface $request, FilesystemInterface $filesystem): ResponseInterface
83
    {
84
        $protected = [
85
            '.htaccess',
86
            '.gitignore',
87
            'index.php',
88
            'config.ini.php',
89
        ];
90
91
        if ($request->getAttribute('dbtype') === 'sqlite') {
92
            $protected[] = $request->getAttribute('dbname') . '.sqlite';
93
        }
94
95
        // Protect the media folders
96
        foreach (Tree::getAll() as $tree) {
97
            $media_directory = $tree->getPreference('MEDIA_DIRECTORY');
98
            [$folder] = explode('/', $media_directory);
99
100
            $protected[] = $folder;
101
        }
102
103
        // List the top-level contents of the data folder
104
        $entries = array_map(static function (array $content) {
105
            return $content['path'];
106
        }, $filesystem->listContents());
107
108
        return $this->viewResponse('admin/clean-data', [
109
            'title'     => I18N::translate('Clean up data folder'),
110
            'entries'   => $entries,
111
            'protected' => $protected,
112
        ]);
113
    }
114
115
    /**
116
     * Delete old user files in the data folder.
117
     *
118
     * @param ServerRequestInterface $request
119
     * @param FilesystemInterface    $filesystem
120
     *
121
     * @return ResponseInterface
122
     */
123
    public function cleanDataAction(ServerRequestInterface $request, FilesystemInterface $filesystem): ResponseInterface
124
    {
125
        $to_delete = $request->getParsedBody()['to_delete'] ?? [];
126
        $to_delete = array_filter($to_delete);
127
128
        foreach ($to_delete as $path) {
129
            $metadata = $filesystem->getMetadata($path);
130
131
            if ($metadata === false) {
132
                // Already deleted?
133
                continue;
134
            }
135
136
            if ($metadata['type'] === 'dir') {
137
                try {
138
                    $filesystem->deleteDir($path);
139
140
                    FlashMessages::addMessage(I18N::translate('The folder %s has been deleted.', e($path)), 'success');
141
                } catch (Exception $ex) {
142
                    FlashMessages::addMessage(I18N::translate('The folder %s could not be deleted.', e($path)), 'danger');
143
                }
144
            }
145
146
            if ($metadata['type'] === 'file') {
147
                try {
148
                    $filesystem->delete($path);
149
150
                    FlashMessages::addMessage(I18N::translate('The file %s has been deleted.', e($path)), 'success');
151
                } catch (Exception $ex) {
152
                    FlashMessages::addMessage(I18N::translate('The file %s could not be deleted.', e($path)), 'danger');
153
                }
154
            }
155
        }
156
157
        return redirect(route('admin-clean-data'));
158
    }
159
160
    /**
161
     * @param ServerRequestInterface $request
162
     *
163
     * @return ResponseInterface
164
     */
165
    public function logs(ServerRequestInterface $request): ResponseInterface
166
    {
167
        $earliest = DB::table('log')->min('log_time');
168
        $latest   = DB::table('log')->max('log_time');
169
170
        $earliest = $earliest ? Carbon::make($earliest) : Carbon::now();
171
        $latest   = $latest ? Carbon::make($latest) : Carbon::now();
172
173
        $earliest = $earliest->toDateString();
0 ignored issues
show
Bug introduced by
The method toDateString() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

173
        /** @scrutinizer ignore-call */ 
174
        $earliest = $earliest->toDateString();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
174
        $latest   = $latest->toDateString();
0 ignored issues
show
Bug introduced by
The method toDateString() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
        /** @scrutinizer ignore-call */ 
175
        $latest   = $latest->toDateString();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
175
176
        $params   = $request->getQueryParams();
177
        $action   = $params['action'] ?? '';
178
        $from     = $params['from'] ?? $earliest;
179
        $to       = $params['to'] ?? $latest;
180
        $type     = $params['type'] ?? '';
181
        $text     = $params['text'] ?? '';
182
        $ip       = $params['ip'] ?? '';
183
        $username = $params['username'] ?? '';
184
        $gedc     = $params['gedc'] ?? '';
185
186
        $from = max($from, $earliest);
187
        $to   = min(max($from, $to), $latest);
188
189
        $user_options = ['' => ''];
190
        foreach ($this->user_service->all() as $tmp_user) {
191
            $user_options[$tmp_user->userName()] = $tmp_user->userName();
192
        }
193
194
        $tree_options = ['' => ''] + Tree::getNameList();
195
196
        $title = I18N::translate('Website logs');
197
198
        return $this->viewResponse('admin/site-logs', [
199
            'action'       => $action,
200
            'earliest'     => $earliest,
201
            'from'         => $from,
202
            'gedc'         => $gedc,
203
            'ip'           => $ip,
204
            'latest'       => $latest,
205
            'tree_options' => $tree_options,
206
            'title'        => $title,
207
            'to'           => $to,
208
            'text'         => $text,
209
            'type'         => $type,
210
            'username'     => $username,
211
            'user_options' => $user_options,
212
        ]);
213
    }
214
215
    /**
216
     * @param ServerRequestInterface $request
217
     * @param DatatablesService      $datatables_service
218
     *
219
     * @return ResponseInterface
220
     */
221
    public function logsData(ServerRequestInterface $request, DatatablesService $datatables_service): ResponseInterface
222
    {
223
        $query = $this->logsQuery($request->getQueryParams());
224
225
        return $datatables_service->handle($request, $query, [], [], static function (stdClass $row): array {
226
            return [
227
                $row->log_id,
228
                Carbon::make($row->log_time)->local()->format('Y-m-d H:i:s'),
229
                $row->log_type,
230
                '<span dir="auto">' . e($row->log_message) . '</span>',
231
                '<span dir="auto">' . e($row->ip_address) . '</span>',
232
                '<span dir="auto">' . e($row->user_name) . '</span>',
233
                '<span dir="auto">' . e($row->gedcom_name) . '</span>',
234
            ];
235
        });
236
    }
237
238
    /**
239
     * Generate a query for filtering the site log.
240
     *
241
     * @param string[] $params
242
     *
243
     * @return Builder
244
     */
245
    private function logsQuery(array $params): Builder
246
    {
247
        $from     = $params['from'];
248
        $to       = $params['to'];
249
        $type     = $params['type'];
250
        $text     = $params['text'];
251
        $ip       = $params['ip'];
252
        $username = $params['username'];
253
        $gedc     = $params['gedc'];
254
255
        $query = DB::table('log')
256
            ->leftJoin('user', 'user.user_id', '=', 'log.user_id')
257
            ->leftJoin('gedcom', 'gedcom.gedcom_id', '=', 'log.gedcom_id')
258
            ->select(['log.*', new Expression("COALESCE(user_name, '<none>') AS user_name"), new Expression("COALESCE(gedcom_name, '<none>') AS gedcom_name")]);
259
260
        if ($from !== '') {
261
            $query->where('log_time', '>=', $from);
262
        }
263
264
        if ($to !== '') {
265
            // before end of the day
266
            $query->where('log_time', '<', Carbon::make($to)->addDay());
267
        }
268
269
        if ($type !== '') {
270
            $query->where('log_type', '=', $type);
271
        }
272
273
        if ($text) {
274
            $query->whereContains('log_message', $text);
275
        }
276
277
        if ($ip) {
278
            $query->whereContains('ip_address', $ip);
279
        }
280
281
        if ($username) {
282
            $query->whereContains('user_name', $ip);
283
        }
284
285
        if ($gedc) {
286
            $query->where('gedcom_name', '=', $gedc);
287
        }
288
289
        return $query;
290
    }
291
292
    /**
293
     * @param ServerRequestInterface $request
294
     *
295
     * @return ResponseInterface
296
     */
297
    public function logsDelete(ServerRequestInterface $request): ResponseInterface
298
    {
299
        $this->logsQuery($request->getParsedBody())->delete();
0 ignored issues
show
Bug introduced by
It seems like $request->getParsedBody() can also be of type null and object; however, parameter $params of Fisharebest\Webtrees\Htt...Controller::logsQuery() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
        $this->logsQuery(/** @scrutinizer ignore-type */ $request->getParsedBody())->delete();
Loading history...
300
301
        return response();
302
    }
303
304
    /**
305
     * @param ServerRequestInterface $request
306
     *
307
     * @return ResponseInterface
308
     */
309
    public function logsExport(ServerRequestInterface $request): ResponseInterface
310
    {
311
        $content = $this->logsQuery($request->getQueryParams())
312
            ->orderBy('log_id')
313
            ->get()
314
            ->map(static function (stdClass $row): string {
315
                return
316
                    '"' . $row->log_time . '",' .
317
                    '"' . $row->log_type . '",' .
318
                    '"' . str_replace('"', '""', $row->log_message) . '",' .
319
                    '"' . $row->ip_address . '",' .
320
                    '"' . str_replace('"', '""', $row->user_name) . '",' .
321
                    '"' . str_replace('"', '""', $row->gedcom_name) . '"' .
322
                    "\n";
323
            })
324
            ->implode('');
325
326
        return response($content, StatusCodeInterface::STATUS_OK, [
327
            'Content-Type'        => 'text/csv; charset=utf-8',
328
            'Content-Disposition' => 'attachment; filename="webtrees-logs.csv"',
329
        ]);
330
    }
331
332
    /**
333
     * @param MailService $mail_service
334
     *
335
     * @return ResponseInterface
336
     */
337
    public function mailForm(MailService $mail_service): ResponseInterface
338
    {
339
        $mail_ssl_options       = $mail_service->mailSslOptions();
340
        $mail_transport_options = $mail_service->mailTransportOptions();
341
342
        $title = I18N::translate('Sending email');
343
344
        $SMTP_ACTIVE    = Site::getPreference('SMTP_ACTIVE');
345
        $SMTP_AUTH      = Site::getPreference('SMTP_AUTH');
346
        $SMTP_AUTH_USER = Site::getPreference('SMTP_AUTH_USER');
347
        $SMTP_FROM_NAME = $mail_service->senderEmail();
348
        $SMTP_HELO      = $mail_service->localDomain();
349
        $SMTP_HOST      = Site::getPreference('SMTP_HOST');
350
        $SMTP_PORT      = Site::getPreference('SMTP_PORT');
351
        $SMTP_SSL       = Site::getPreference('SMTP_SSL');
352
        $DKIM_DOMAIN    = Site::getPreference('DKIM_DOMAIN');
353
        $DKIM_SELECTOR  = Site::getPreference('DKIM_SELECTOR');
354
        $DKIM_KEY       = Site::getPreference('DKIM_KEY');
355
356
        $smtp_from_name_valid = $mail_service->isValidEmail($SMTP_FROM_NAME);
357
        $smtp_helo_valid      = filter_var($SMTP_HELO, FILTER_VALIDATE_DOMAIN);
358
359
        return $this->viewResponse('admin/site-mail', [
360
            'mail_ssl_options'       => $mail_ssl_options,
361
            'mail_transport_options' => $mail_transport_options,
362
            'title'                  => $title,
363
            'smtp_helo_valid'        => $smtp_helo_valid,
364
            'smtp_from_name_valid'   => $smtp_from_name_valid,
365
            'SMTP_ACTIVE'            => $SMTP_ACTIVE,
366
            'SMTP_AUTH'              => $SMTP_AUTH,
367
            'SMTP_AUTH_USER'         => $SMTP_AUTH_USER,
368
            'SMTP_FROM_NAME'         => $SMTP_FROM_NAME,
369
            'SMTP_HELO'              => $SMTP_HELO,
370
            'SMTP_HOST'              => $SMTP_HOST,
371
            'SMTP_PORT'              => $SMTP_PORT,
372
            'SMTP_SSL'               => $SMTP_SSL,
373
            'DKIM_DOMAIN'            => $DKIM_DOMAIN,
374
            'DKIM_SELECTOR'          => $DKIM_SELECTOR,
375
            'DKIM_KEY'               => $DKIM_KEY,
376
        ]);
377
    }
378
379
    /**
380
     * @param ServerRequestInterface $request
381
     *
382
     * @return ResponseInterface
383
     */
384
    public function mailSave(ServerRequestInterface $request): ResponseInterface
385
    {
386
        $params = $request->getParsedBody();
387
388
        Site::setPreference('SMTP_ACTIVE', $params['SMTP_ACTIVE']);
389
        Site::setPreference('SMTP_FROM_NAME', $params['SMTP_FROM_NAME']);
390
        Site::setPreference('SMTP_HOST', $params['SMTP_HOST']);
391
        Site::setPreference('SMTP_PORT', $params['SMTP_PORT']);
392
        Site::setPreference('SMTP_AUTH', $params['SMTP_AUTH']);
393
        Site::setPreference('SMTP_AUTH_USER', $params['SMTP_AUTH_USER']);
394
        Site::setPreference('SMTP_SSL', $params['SMTP_SSL']);
395
        Site::setPreference('SMTP_HELO', $params['SMTP_HELO']);
396
        Site::setPreference('DKIM_DOMAIN', $params['DKIM_DOMAIN']);
397
        Site::setPreference('DKIM_SELECTOR', $params['DKIM_SELECTOR']);
398
        Site::setPreference('DKIM_KEY', $params['DKIM_KEY']);
399
400
        if ($params['SMTP_AUTH_PASS'] !== '') {
401
            Site::setPreference('SMTP_AUTH_PASS', $params['SMTP_AUTH_PASS']);
402
        }
403
404
        FlashMessages::addMessage(I18N::translate('The website preferences have been updated.'), 'success');
405
        $url = route('admin-control-panel');
406
407
        return redirect($url);
408
    }
409
410
    /**
411
     * @return ResponseInterface
412
     */
413
    public function preferencesForm(): ResponseInterface
414
    {
415
        $all_themes = $this->themeOptions();
416
417
        $title = I18N::translate('Website preferences');
418
419
        return $this->viewResponse('admin/site-preferences', [
420
            'all_themes'         => $all_themes,
421
            'max_execution_time' => (int) get_cfg_var('max_execution_time'),
422
            'title'              => $title,
423
        ]);
424
    }
425
426
    /**
427
     * @return Collection
428
     */
429
    private function themeOptions(): Collection
430
    {
431
        return $this->module_service
432
            ->findByInterface(ModuleThemeInterface::class)
433
            ->map($this->module_service->titleMapper());
434
    }
435
436
    /**
437
     * @param ServerRequestInterface $request
438
     *
439
     * @return ResponseInterface
440
     */
441
    public function preferencesSave(ServerRequestInterface $request): ResponseInterface
442
    {
443
        $params = $request->getParsedBody();
444
445
        $INDEX_DIRECTORY = $params['INDEX_DIRECTORY'];
446
        if (substr($INDEX_DIRECTORY, -1) !== '/') {
447
            $INDEX_DIRECTORY .= '/';
448
        }
449
        if (File::mkdir($INDEX_DIRECTORY)) {
450
            Site::setPreference('INDEX_DIRECTORY', $INDEX_DIRECTORY);
451
        } else {
452
            FlashMessages::addMessage(I18N::translate('The folder %s does not exist, and it could not be created.', e($INDEX_DIRECTORY)), 'danger');
453
        }
454
455
        Site::setPreference('THEME_DIR', $params['THEME_DIR']);
456
        Site::setPreference('ALLOW_CHANGE_GEDCOM', $params['ALLOW_CHANGE_GEDCOM']);
457
        Site::setPreference('TIMEZONE', $params['TIMEZONE']);
458
459
        FlashMessages::addMessage(I18N::translate('The website preferences have been updated.'), 'success');
460
        $url = route('admin-control-panel');
461
462
        return redirect($url);
463
    }
464
465
    /**
466
     * @return ResponseInterface
467
     */
468
    public function registrationForm(): ResponseInterface
469
    {
470
        $title = I18N::translate('Sign-in and registration');
471
472
        $registration_text_options = $this->registrationTextOptions();
473
474
        return $this->viewResponse('admin/site-registration', [
475
            'registration_text_options' => $registration_text_options,
476
            'title'                     => $title,
477
        ]);
478
    }
479
480
    /**
481
     * A list of registration rules (e.g. for an edit control).
482
     *
483
     * @return string[]
484
     */
485
    private function registrationTextOptions(): array
486
    {
487
        return [
488
            0 => I18N::translate('No predefined text'),
489
            1 => I18N::translate('Predefined text that states all users can request a user account'),
490
            2 => I18N::translate('Predefined text that states admin will decide on each request for a user account'),
491
            3 => I18N::translate('Predefined text that states only family members can request a user account'),
492
            4 => I18N::translate('Choose user defined welcome text typed below'),
493
        ];
494
    }
495
496
    /**
497
     * @param ServerRequestInterface $request
498
     *
499
     * @return ResponseInterface
500
     */
501
    public function registrationSave(ServerRequestInterface $request): ResponseInterface
502
    {
503
        $params = $request->getParsedBody();
504
505
        Site::setPreference('WELCOME_TEXT_AUTH_MODE', $params['WELCOME_TEXT_AUTH_MODE']);
506
        Site::setPreference('WELCOME_TEXT_AUTH_MODE_' . WT_LOCALE, $params['WELCOME_TEXT_AUTH_MODE_4']);
507
        Site::setPreference('USE_REGISTRATION_MODULE', $params['USE_REGISTRATION_MODULE']);
508
        Site::setPreference('SHOW_REGISTER_CAUTION', $params['SHOW_REGISTER_CAUTION']);
509
510
        FlashMessages::addMessage(I18N::translate('The website preferences have been updated.'), 'success');
511
        $url = route('admin-control-panel');
512
513
        return redirect($url);
514
    }
515
516
    /**
517
     * Show the server information page.
518
     *
519
     * @return ResponseInterface
520
     */
521
    public function serverInformation(): ResponseInterface
522
    {
523
        ob_start();
524
        phpinfo(INFO_ALL & ~INFO_CREDITS & ~INFO_LICENSE);
525
        $phpinfo = ob_get_clean();
526
        preg_match('%<body>(.*)</body>%s', $phpinfo, $matches);
527
        $phpinfo = $matches[1];
528
529
        return $this->viewResponse('admin/server-information', [
530
            'title'   => I18N::translate('Server information'),
531
            'phpinfo' => $phpinfo,
532
        ]);
533
    }
534
}
535