xref: /webtrees/app/Http/RequestHandlers/ControlPanel.php (revision 24f2a3af38709f9bf0a739b30264240d20ba34e8)
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 <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Http\RequestHandlers;
21
22use Fisharebest\Webtrees\Http\ViewResponseTrait;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Module\FamilyListModule;
25use Fisharebest\Webtrees\Module\IndividualListModule;
26use Fisharebest\Webtrees\Module\MediaListModule;
27use Fisharebest\Webtrees\Module\ModuleAnalyticsInterface;
28use Fisharebest\Webtrees\Module\ModuleBlockInterface;
29use Fisharebest\Webtrees\Module\ModuleChartInterface;
30use Fisharebest\Webtrees\Module\ModuleCustomInterface;
31use Fisharebest\Webtrees\Module\ModuleDataFixInterface;
32use Fisharebest\Webtrees\Module\ModuleFooterInterface;
33use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface;
34use Fisharebest\Webtrees\Module\ModuleLanguageInterface;
35use Fisharebest\Webtrees\Module\ModuleListInterface;
36use Fisharebest\Webtrees\Module\ModuleMenuInterface;
37use Fisharebest\Webtrees\Module\ModuleReportInterface;
38use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
39use Fisharebest\Webtrees\Module\ModuleTabInterface;
40use Fisharebest\Webtrees\Module\ModuleThemeInterface;
41use Fisharebest\Webtrees\Module\NoteListModule;
42use Fisharebest\Webtrees\Module\RepositoryListModule;
43use Fisharebest\Webtrees\Module\SourceListModule;
44use Fisharebest\Webtrees\Module\SubmitterListModule;
45use Fisharebest\Webtrees\Note;
46use Fisharebest\Webtrees\Registry;
47use Fisharebest\Webtrees\Repository;
48use Fisharebest\Webtrees\Services\AdminService;
49use Fisharebest\Webtrees\Services\HousekeepingService;
50use Fisharebest\Webtrees\Services\ModuleService;
51use Fisharebest\Webtrees\Services\ServerCheckService;
52use Fisharebest\Webtrees\Services\TreeService;
53use Fisharebest\Webtrees\Services\UpgradeService;
54use Fisharebest\Webtrees\Services\UserService;
55use Fisharebest\Webtrees\Submitter;
56use Fisharebest\Webtrees\Webtrees;
57use Illuminate\Database\Capsule\Manager as DB;
58use Illuminate\Database\Query\Expression;
59use Illuminate\Database\Query\JoinClause;
60use Illuminate\Support\Collection;
61use League\Flysystem\Adapter\Local;
62use League\Flysystem\Filesystem;
63use Psr\Http\Message\ResponseInterface;
64use Psr\Http\Message\ServerRequestInterface;
65use Psr\Http\Server\RequestHandlerInterface;
66
67/**
68 * The control panel shows a summary of the site and links to admin functions.
69 */
70class ControlPanel implements RequestHandlerInterface
71{
72    use ViewResponseTrait;
73
74    /** @var AdminService */
75    private $admin_service;
76
77    /** @var ModuleService */
78    private $module_service;
79
80    /** @var HousekeepingService */
81    private $housekeeping_service;
82
83    /** @var ServerCheckService */
84    private $server_check_service;
85
86    /** @var TreeService */
87    private $tree_service;
88
89    /** @var UpgradeService */
90    private $upgrade_service;
91
92    /** @var UserService */
93    private $user_service;
94
95    /**
96     * ControlPanel constructor.
97     *
98     * @param AdminService        $admin_service
99     * @param HousekeepingService $housekeeping_service
100     * @param ModuleService       $module_service
101     * @param ServerCheckService  $server_check_service
102     * @param TreeService         $tree_service
103     * @param UpgradeService      $upgrade_service
104     * @param UserService         $user_service
105     */
106    public function __construct(
107        AdminService $admin_service,
108        HousekeepingService $housekeeping_service,
109        ModuleService $module_service,
110        ServerCheckService $server_check_service,
111        TreeService $tree_service,
112        UpgradeService $upgrade_service,
113        UserService $user_service
114    ) {
115        $this->admin_service        = $admin_service;
116        $this->housekeeping_service = $housekeeping_service;
117        $this->module_service       = $module_service;
118        $this->server_check_service = $server_check_service;
119        $this->tree_service         = $tree_service;
120        $this->upgrade_service      = $upgrade_service;
121        $this->user_service         = $user_service;
122    }
123
124    /**
125     * @param ServerRequestInterface $request
126     *
127     * @return ResponseInterface
128     */
129    public function handle(ServerRequestInterface $request): ResponseInterface
130    {
131        $this->layout = 'layouts/administration';
132
133        $filesystem      = new Filesystem(new Local(Webtrees::ROOT_DIR));
134        $files_to_delete = $this->housekeeping_service->deleteOldWebtreesFiles($filesystem);
135
136        $custom_updates = $this->module_service
137            ->findByInterface(ModuleCustomInterface::class)
138            ->filter(static function (ModuleCustomInterface $module): bool {
139                return version_compare($module->customModuleLatestVersion(), $module->customModuleVersion()) > 0;
140            });
141
142        $multiple_tree_threshold = $this->admin_service->multipleTreeThreshold();
143        $gedcom_file_count       = $this->admin_service->gedcomFiles(Registry::filesystem()->data())->count();
144
145        return $this->viewResponse('admin/control-panel', [
146            'title'                      => I18N::translate('Control panel'),
147            'server_errors'              => $this->server_check_service->serverErrors(),
148            'server_warnings'            => $this->server_check_service->serverWarnings(),
149            'latest_version'             => $this->upgrade_service->latestVersion(),
150            'all_users'                  => $this->user_service->all(),
151            'administrators'             => $this->user_service->administrators(),
152            'managers'                   => $this->user_service->managers(),
153            'moderators'                 => $this->user_service->moderators(),
154            'unapproved'                 => $this->user_service->unapproved(),
155            'unverified'                 => $this->user_service->unverified(),
156            'all_trees'                  => $this->tree_service->all(),
157            'changes'                    => $this->totalChanges(),
158            'individuals'                => $this->totalIndividuals(),
159            'families'                   => $this->totalFamilies(),
160            'sources'                    => $this->totalSources(),
161            'media'                      => $this->totalMediaObjects(),
162            'repositories'               => $this->totalRepositories(),
163            'notes'                      => $this->totalNotes(),
164            'submitters'                 => $this->totalSubmitters(),
165            'individual_list_module'     => $this->module_service->findByInterface(IndividualListModule::class)->first(),
166            'family_list_module'         => $this->module_service->findByInterface(FamilyListModule::class)->first(),
167            'media_list_module'          => $this->module_service->findByInterface(MediaListModule::class)->first(),
168            'note_list_module'           => $this->module_service->findByInterface(NoteListModule::class)->first(),
169            'repository_list_module'     => $this->module_service->findByInterface(RepositoryListModule::class)->first(),
170            'source_list_module'         => $this->module_service->findByInterface(SourceListModule::class)->first(),
171            'submitter_list_module'      => $this->module_service->findByInterface(SubmitterListModule::class)->first(),
172            'files_to_delete'            => $files_to_delete,
173            'all_modules_disabled'       => $this->module_service->all(true),
174            'all_modules_enabled'        => $this->module_service->all(),
175            'deleted_modules'            => $this->module_service->deletedModules(),
176            'analytics_modules_disabled' => $this->module_service->findByInterface(ModuleAnalyticsInterface::class, true),
177            'analytics_modules_enabled'  => $this->module_service->findByInterface(ModuleAnalyticsInterface::class),
178            'block_modules_disabled'     => $this->module_service->findByInterface(ModuleBlockInterface::class, true),
179            'block_modules_enabled'      => $this->module_service->findByInterface(ModuleBlockInterface::class),
180            'chart_modules_disabled'     => $this->module_service->findByInterface(ModuleChartInterface::class, true),
181            'chart_modules_enabled'      => $this->module_service->findByInterface(ModuleChartInterface::class),
182            'custom_updates'             => $custom_updates,
183            'data_fix_modules_disabled'  => $this->module_service->findByInterface(ModuleDataFixInterface::class, true),
184            'data_fix_modules_enabled'   => $this->module_service->findByInterface(ModuleDataFixInterface::class),
185            'other_modules'              => $this->module_service->otherModules(true),
186            'footer_modules_disabled'    => $this->module_service->findByInterface(ModuleFooterInterface::class, true),
187            'footer_modules_enabled'     => $this->module_service->findByInterface(ModuleFooterInterface::class),
188            'history_modules_disabled'   => $this->module_service->findByInterface(ModuleHistoricEventsInterface::class, true),
189            'history_modules_enabled'    => $this->module_service->findByInterface(ModuleHistoricEventsInterface::class),
190            'language_modules_disabled'  => $this->module_service->findByInterface(ModuleLanguageInterface::class, true),
191            'language_modules_enabled'   => $this->module_service->findByInterface(ModuleLanguageInterface::class),
192            'list_modules_disabled'      => $this->module_service->findByInterface(ModuleListInterface::class, true),
193            'list_modules_enabled'       => $this->module_service->findByInterface(ModuleListInterface::class),
194            'menu_modules_disabled'      => $this->module_service->findByInterface(ModuleMenuInterface::class, true),
195            'menu_modules_enabled'       => $this->module_service->findByInterface(ModuleMenuInterface::class),
196            'report_modules_disabled'    => $this->module_service->findByInterface(ModuleReportInterface::class, true),
197            'report_modules_enabled'     => $this->module_service->findByInterface(ModuleReportInterface::class),
198            'sidebar_modules_disabled'   => $this->module_service->findByInterface(ModuleSidebarInterface::class, true),
199            'sidebar_modules_enabled'    => $this->module_service->findByInterface(ModuleSidebarInterface::class),
200            'tab_modules_disabled'       => $this->module_service->findByInterface(ModuleTabInterface::class, true),
201            'tab_modules_enabled'        => $this->module_service->findByInterface(ModuleTabInterface::class),
202            'theme_modules_disabled'     => $this->module_service->findByInterface(ModuleThemeInterface::class, true),
203            'theme_modules_enabled'      => $this->module_service->findByInterface(ModuleThemeInterface::class),
204            'show_synchronize'           => $gedcom_file_count >= $multiple_tree_threshold,
205        ]);
206    }
207
208    /**
209     * Count the number of pending changes in each tree.
210     *
211     * @return array<string>
212     */
213    private function totalChanges(): array
214    {
215        return DB::table('gedcom')
216            ->leftJoin('change', static function (JoinClause $join): void {
217                $join
218                    ->on('change.gedcom_id', '=', 'gedcom.gedcom_id')
219                    ->where('change.status', '=', 'pending');
220            })
221            ->groupBy(['gedcom.gedcom_id'])
222            ->pluck(new Expression('COUNT(change_id) AS aggregate'), 'gedcom.gedcom_id')
223            ->all();
224    }
225
226    /**
227     * Count the number of individuals in each tree.
228     *
229     * @return Collection<string,int>
230     */
231    private function totalIndividuals(): Collection
232    {
233        return DB::table('gedcom')
234            ->leftJoin('individuals', 'i_file', '=', 'gedcom_id')
235            ->groupBy(['gedcom_id'])
236            ->pluck(new Expression('COUNT(i_id) AS aggregate'), 'gedcom_id')
237            ->map(static function (string $count) {
238                return (int) $count;
239            });
240    }
241
242    /**
243     * Count the number of families in each tree.
244     *
245     * @return Collection<string,int>
246     */
247    private function totalFamilies(): Collection
248    {
249        return DB::table('gedcom')
250            ->leftJoin('families', 'f_file', '=', 'gedcom_id')
251            ->groupBy(['gedcom_id'])
252            ->pluck(new Expression('COUNT(f_id) AS aggregate'), 'gedcom_id')
253            ->map(static function (string $count) {
254                return (int) $count;
255            });
256    }
257
258    /**
259     * Count the number of sources in each tree.
260     *
261     * @return Collection<string,int>
262     */
263    private function totalSources(): Collection
264    {
265        return DB::table('gedcom')
266            ->leftJoin('sources', 's_file', '=', 'gedcom_id')
267            ->groupBy(['gedcom_id'])
268            ->pluck(new Expression('COUNT(s_id) AS aggregate'), 'gedcom_id')
269            ->map(static function (string $count) {
270                return (int) $count;
271            });
272    }
273
274    /**
275     * Count the number of media objects in each tree.
276     *
277     * @return Collection<string,int>
278     */
279    private function totalMediaObjects(): Collection
280    {
281        return DB::table('gedcom')
282            ->leftJoin('media', 'm_file', '=', 'gedcom_id')
283            ->groupBy(['gedcom_id'])
284            ->pluck(new Expression('COUNT(m_id) AS aggregate'), 'gedcom_id')
285            ->map(static function (string $count) {
286                return (int) $count;
287            });
288    }
289
290    /**
291     * Count the number of repositorie in each tree.
292     *
293     * @return Collection<string,int>
294     */
295    private function totalRepositories(): Collection
296    {
297        return DB::table('gedcom')
298            ->leftJoin('other', static function (JoinClause $join): void {
299                $join
300                    ->on('o_file', '=', 'gedcom_id')
301                    ->where('o_type', '=', Repository::RECORD_TYPE);
302            })
303            ->groupBy(['gedcom_id'])
304            ->pluck(new Expression('COUNT(o_id) AS aggregate'), 'gedcom_id')
305            ->map(static function (string $count) {
306                return (int) $count;
307            });
308    }
309
310    /**
311     * Count the number of notes in each tree.
312     *
313     * @return Collection<string,int>
314     */
315    private function totalNotes(): Collection
316    {
317        return DB::table('gedcom')
318            ->leftJoin('other', static function (JoinClause $join): void {
319                $join
320                    ->on('o_file', '=', 'gedcom_id')
321                    ->where('o_type', '=', Note::RECORD_TYPE);
322            })
323            ->groupBy(['gedcom_id'])
324            ->pluck(new Expression('COUNT(o_id) AS aggregate'), 'gedcom_id')
325            ->map(static function (string $count) {
326                return (int) $count;
327            });
328    }
329
330    /**
331     * Count the number of submitters in each tree.
332     *
333     * @return Collection<string,int>
334     */
335    private function totalSubmitters(): Collection
336    {
337        return DB::table('gedcom')
338            ->leftJoin('other', static function (JoinClause $join): void {
339                $join
340                    ->on('o_file', '=', 'gedcom_id')
341                    ->where('o_type', '=', Submitter::RECORD_TYPE);
342            })
343            ->groupBy(['gedcom_id'])
344            ->pluck(new Expression('COUNT(o_id) AS aggregate'), 'gedcom_id')
345            ->map(static function (string $count) {
346                return (int) $count;
347            });
348    }
349}
350