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