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