xref: /webtrees/resources/views/admin/control-panel.phtml (revision c540224494b4c0b9f7d890f59f1cd44e9f6d92ea)
1<?php
2
3declare(strict_types=1);
4
5use Fisharebest\Webtrees\Contracts\TimestampInterface;
6use Fisharebest\Webtrees\Http\RequestHandlers\BroadcastPage;
7use Fisharebest\Webtrees\Http\RequestHandlers\CheckForNewVersionNow;
8use Fisharebest\Webtrees\Http\RequestHandlers\CleanDataFolder;
9use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
10use Fisharebest\Webtrees\Http\RequestHandlers\CreateTreePage;
11use Fisharebest\Webtrees\Http\RequestHandlers\EmailPreferencesPage;
12use Fisharebest\Webtrees\Http\RequestHandlers\FixLevel0MediaPage;
13use Fisharebest\Webtrees\Http\RequestHandlers\ImportThumbnailsPage;
14use Fisharebest\Webtrees\Http\RequestHandlers\ManageMediaPage;
15use Fisharebest\Webtrees\Http\RequestHandlers\ManageTrees;
16use Fisharebest\Webtrees\Http\RequestHandlers\MapDataList;
17use Fisharebest\Webtrees\Http\RequestHandlers\MergeTreesPage;
18use Fisharebest\Webtrees\Http\RequestHandlers\ModulesAllPage;
19use Fisharebest\Webtrees\Http\RequestHandlers\ModulesAnalyticsPage;
20use Fisharebest\Webtrees\Http\RequestHandlers\ModulesBlocksPage;
21use Fisharebest\Webtrees\Http\RequestHandlers\ModulesChartsPage;
22use Fisharebest\Webtrees\Http\RequestHandlers\ModulesDataFixesPage;
23use Fisharebest\Webtrees\Http\RequestHandlers\ModulesFootersPage;
24use Fisharebest\Webtrees\Http\RequestHandlers\ModulesHistoricEventsPage;
25use Fisharebest\Webtrees\Http\RequestHandlers\ModulesLanguagesPage;
26use Fisharebest\Webtrees\Http\RequestHandlers\ModulesListsPage;
27use Fisharebest\Webtrees\Http\RequestHandlers\ModulesMapAutocompletePage;
28use Fisharebest\Webtrees\Http\RequestHandlers\ModulesMapGeoLocationsPage;
29use Fisharebest\Webtrees\Http\RequestHandlers\ModulesMapLinksPage;
30use Fisharebest\Webtrees\Http\RequestHandlers\ModulesMapProvidersPage;
31use Fisharebest\Webtrees\Http\RequestHandlers\ModulesMenusPage;
32use Fisharebest\Webtrees\Http\RequestHandlers\ModulesReportsPage;
33use Fisharebest\Webtrees\Http\RequestHandlers\ModulesSharesPage;
34use Fisharebest\Webtrees\Http\RequestHandlers\ModulesSidebarsPage;
35use Fisharebest\Webtrees\Http\RequestHandlers\ModulesTabsPage;
36use Fisharebest\Webtrees\Http\RequestHandlers\ModulesThemesPage;
37use Fisharebest\Webtrees\Http\RequestHandlers\PendingChanges;
38use Fisharebest\Webtrees\Http\RequestHandlers\PhpInformation;
39use Fisharebest\Webtrees\Http\RequestHandlers\SiteLogsPage;
40use Fisharebest\Webtrees\Http\RequestHandlers\SitePreferencesPage;
41use Fisharebest\Webtrees\Http\RequestHandlers\SiteRegistrationPage;
42use Fisharebest\Webtrees\Http\RequestHandlers\SiteTagsPage;
43use Fisharebest\Webtrees\Http\RequestHandlers\TreePageDefaultEdit;
44use Fisharebest\Webtrees\Http\RequestHandlers\UpgradeWizardPage;
45use Fisharebest\Webtrees\Http\RequestHandlers\UploadMediaPage;
46use Fisharebest\Webtrees\Http\RequestHandlers\UserAddPage;
47use Fisharebest\Webtrees\Http\RequestHandlers\UserEditPage;
48use Fisharebest\Webtrees\Http\RequestHandlers\UserListPage;
49use Fisharebest\Webtrees\Http\RequestHandlers\UserPageDefaultEdit;
50use Fisharebest\Webtrees\Http\RequestHandlers\UsersCleanupPage;
51use Fisharebest\Webtrees\I18N;
52use Fisharebest\Webtrees\Module\FamilyListModule;
53use Fisharebest\Webtrees\Module\IndividualListModule;
54use Fisharebest\Webtrees\Module\MediaListModule;
55use Fisharebest\Webtrees\Module\ModuleAnalyticsInterface;
56use Fisharebest\Webtrees\Module\ModuleBlockInterface;
57use Fisharebest\Webtrees\Module\ModuleChartInterface;
58use Fisharebest\Webtrees\Module\ModuleConfigInterface;
59use Fisharebest\Webtrees\Module\ModuleCustomInterface;
60use Fisharebest\Webtrees\Module\ModuleDataFixInterface;
61use Fisharebest\Webtrees\Module\ModuleFooterInterface;
62use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface;
63use Fisharebest\Webtrees\Module\ModuleInterface;
64use Fisharebest\Webtrees\Module\ModuleLanguageInterface;
65use Fisharebest\Webtrees\Module\ModuleListInterface;
66use Fisharebest\Webtrees\Module\ModuleMapAutocompleteInterface;
67use Fisharebest\Webtrees\Module\ModuleMapGeoLocationInterface;
68use Fisharebest\Webtrees\Module\ModuleMapLinkInterface;
69use Fisharebest\Webtrees\Module\ModuleMapProviderInterface;
70use Fisharebest\Webtrees\Module\ModuleMenuInterface;
71use Fisharebest\Webtrees\Module\ModuleReportInterface;
72use Fisharebest\Webtrees\Module\ModuleShareInterface;
73use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
74use Fisharebest\Webtrees\Module\ModuleTabInterface;
75use Fisharebest\Webtrees\Module\ModuleThemeInterface;
76use Fisharebest\Webtrees\Module\NoteListModule;
77use Fisharebest\Webtrees\Module\RepositoryListModule;
78use Fisharebest\Webtrees\Module\SourceListModule;
79use Fisharebest\Webtrees\Module\SubmitterListModule;
80use Fisharebest\Webtrees\Tree;
81use Fisharebest\Webtrees\User;
82use Fisharebest\Webtrees\Webtrees;
83use Illuminate\Support\Collection;
84
85/**
86 * @var Collection<int,User>                           $administrators
87 * @var Collection<int,ModuleInterface>                $all_modules_disabled
88 * @var Collection<int,ModuleInterface>                $all_modules_enabled
89 * @var Collection<int,Tree>                           $all_trees
90 * @var Collection<int,User>                           $all_users
91 * @var Collection<int,ModuleAnalyticsInterface>       $analytics_modules_disabled
92 * @var Collection<int,ModuleAnalyticsInterface>       $analytics_modules_enabled
93 * @var Collection<int,ModuleBlockInterface>           $block_modules_disabled
94 * @var Collection<int,ModuleBlockInterface>           $block_modules_enabled
95 * @var array<string,int>                              $changes
96 * @var Collection<int,ModuleChartInterface>           $chart_modules_disabled
97 * @var Collection<int,ModuleChartInterface>           $chart_modules_enabled
98 * @var Collection<int,ModuleCustomInterface>          $custom_updates
99 * @var Collection<int,ModuleDataFixInterface>         $data_fix_modules_disabled
100 * @var Collection<int,ModuleDataFixInterface>         $data_fix_modules_enabled
101 * @var Collection<array-key,int>                      $families
102 * @var FamilyListModule|null                          $family_list_module
103 * @var Collection<int,ModuleFooterInterface>          $footer_modules_disabled
104 * @var Collection<int,ModuleFooterInterface>          $footer_modules_enabled
105 * @var Collection<int,ModuleHistoricEventsInterface>  $history_modules_disabled
106 * @var Collection<int,ModuleHistoricEventsInterface>  $history_modules_enabled
107 * @var Collection<array-key,int>                      $individuals
108 * @var IndividualListModule|null                      $individual_list_module
109 * @var Collection<int,ModuleLanguageInterface>        $language_modules_disabled
110 * @var Collection<int,ModuleLanguageInterface>        $language_modules_enabled
111 * @var string                                         $latest_version
112 * @var string                                         $latest_version_error
113 * @var TimestampInterface                             $latest_version_timestamp
114 * @var Collection<int,ModuleListInterface>            $list_modules_disabled
115 * @var Collection<int,ModuleListInterface>            $list_modules_enabled
116 * @var Collection<int,ModuleMapLinkInterface>         $map_link_modules_disabled
117 * @var Collection<int,ModuleMapLinkInterface>         $map_link_modules_enabled
118 * @var Collection<int,ModuleMapAutocompleteInterface> $map_autocomplete_modules_disabled
119 * @var Collection<int,ModuleMapAutocompleteInterface> $map_autocomplete_modules_enabled
120 * @var Collection<int,ModuleMapProviderInterface>     $map_provider_modules_disabled
121 * @var Collection<int,ModuleMapProviderInterface>     $map_provider_modules_enabled
122 * @var Collection<int,ModuleMapGeoLocationInterface>  $map_search_modules_disabled
123 * @var Collection<int,ModuleMapGeoLocationInterface>  $map_search_modules_enabled
124 * @var Collection<int,User>                           $managers
125 * @var Collection<array-key,int>                      $media
126 * @var MediaListModule|null                           $media_list_module
127 * @var Collection<int,ModuleMenuInterface>            $menu_modules_disabled
128 * @var Collection<int,ModuleMenuInterface>            $menu_modules_enabled
129 * @var Collection<int,User>                           $moderators
130 * @var Collection<array-key,int>                      $notes
131 * @var NoteListModule|null                            $note_list_module
132 * @var Collection<int,ModuleInterface>                $other_modules
133 * @var array<string,string>                           $recipients
134 * @var Collection<int,ModuleReportInterface>          $report_modules_disabled
135 * @var Collection<int,ModuleReportInterface>          $report_modules_enabled
136 * @var Collection<array-key,int>                      $repositories
137 * @var RepositoryListModule|null                      $repository_list_module
138 * @var Collection<int,string>                         $server_errors
139 * @var Collection<int,string>                         $server_warnings
140 * @var bool                                           $show_synchronize
141 * @var Collection<int,ModuleShareInterface>           $share_modules_disabled
142 * @var Collection<int,ModuleShareInterface>           $share_modules_enabled
143 * @var Collection<int,ModuleSidebarInterface>         $sidebar_modules_disabled
144 * @var Collection<int,ModuleSidebarInterface>         $sidebar_modules_enabled
145 * @var Collection<array-key,int>                      $sources
146 * @var SourceListModule|null                          $source_list_module
147 * @var Collection<array-key,int>                      $submitters
148 * @var SubmitterListModule|null                       $submitter_list_module
149 * @var Collection<int,ModuleTabInterface>             $tab_modules_disabled
150 * @var Collection<int,ModuleTabInterface>             $tab_modules_enabled
151 * @var Collection<int,ModuleThemeInterface>           $theme_modules_disabled
152 * @var Collection<int,ModuleThemeInterface>           $theme_modules_enabled
153 * @var string                                         $title
154 * @var Collection<int,User>                           $unapproved
155 * @var Collection<int,User>                           $unverified
156 */
157
158?>
159
160<h1><?= $title ?></h1>
161
162<!-- WEBSITE / WEBTREES -->
163<div class="card mb-4">
164    <div class="card-header">
165        <h2 class="mb-0">
166            <?= I18N::translate('Website') ?>
167            <span class="badge bg-secondary" dir="ltr">
168                <?= e(Webtrees::VERSION) ?>
169            </span>
170        </h2>
171    </div>
172    <div class="card-body">
173        <?php foreach ($server_errors as $server_error) : ?>
174            <p class="alert alert-danger"><?= $server_error ?></p>
175        <?php endforeach ?>
176
177        <?php foreach ($server_warnings as $server_warning) : ?>
178            <p class="alert alert-warning"><?= $server_warning ?></p>
179        <?php endforeach ?>
180
181        <p class="card-text">
182            <?= /* I18N: %s is a URL/link to the project website */
183            I18N::translate('Support and documentation can be found at %s.', '<a href="https://webtrees.net/">webtrees.net</a>') ?>
184        </p>
185
186        <?php if ($latest_version_error !== '') : ?>
187            <p class="alert alert-danger">
188                <?= I18N::translate('There was an error checking for a new version.') ?>
189                <br>
190                <?= $latest_version_error ?>
191                <br>
192                <?= /* I18N: Last checked X hours ago. */ I18N::translate('Last checked %s.', $latest_version_timestamp->diffForHumans()) ?>
193                <a href="#" class="btn btn-primary" data-wt-post-url="<?= e(route(CheckForNewVersionNow::class)) ?>">
194                    <?= /* I18N: button label */ I18N::translate('check now') ?>
195                </a>
196            </p>
197        <?php elseif ($latest_version === '') : ?>
198            <p class="card-text">
199                <?= I18N::translate('No upgrade information is available.') ?>
200            </p>
201        <?php elseif (version_compare(Webtrees::VERSION, $latest_version) < 0) : ?>
202            <p class="card-text alert alert-info">
203                <?= I18N::translate('A new version of webtrees is available.') ?>
204                <a href="<?= e(route(UpgradeWizardPage::class)) ?>" class="alert-link">
205                    <?= /* I18N: %s is a version number */
206                    I18N::translate('Upgrade to webtrees %s.', e($latest_version)) ?>
207                </a>
208            </p>
209        <?php else : ?>
210            <p class="card-text">
211                <?= I18N::translate('This is the latest version of webtrees. No upgrade is available.') ?>
212                <?= /* I18N: Last checked X hours ago. */ I18N::translate('Last checked %s.', $latest_version_timestamp->diffForHumans()) ?>
213                <?php if ($latest_version_timestamp->timestamp() + 60 < time()) : ?>
214                    <a href="#" class="btn btn-primary" data-wt-post-url="<?= e(route(CheckForNewVersionNow::class)) ?>">
215                        <?= /* I18N: button label */ I18N::translate('check now') ?>
216                    </a>
217                <?php endif ?>
218            </p>
219        <?php endif ?>
220
221        <?php foreach ($custom_updates as $module) : ?>
222        <div class="alert alert-info">
223            <?= view('icons/information') ?>
224            <?= I18N::translate('An upgrade is available.') ?>
225            <br>
226            <?= $module->title() ?><?= e($module->customModuleVersion()) ?><?= e($module->customModuleLatestVersion()) ?>
227            <?php if ($module->customModuleSupportUrl() !== '') : ?>
228                <br>
229                <?= I18N::translate('For more information, see %s.', '<a href="' . e($module->customModuleSupportUrl()) . '">' . e($module->customModuleSupportUrl()) . '</a>') ?>
230            <?php endif ?>
231        </div>
232        <?php endforeach ?>
233
234        <div class="row">
235            <div class="col-sm">
236                <ul class="fa-ul mx-0">
237                    <li>
238                        <span class="fa-li"><?= view('icons/preferences') ?></span>
239                        <a href="<?= e(route(SitePreferencesPage::class)) ?>">
240                            <?= I18N::translate('Website preferences') ?>
241                        </a>
242                    </li>
243                    <li>
244                        <span class="fa-li"><?= view('icons/email') ?></span>
245                        <a href="<?= e(route(EmailPreferencesPage::class)) ?>">
246                            <?= I18N::translate('Sending email') ?>
247                        </a>
248                    </li>
249                    <li>
250                        <span class="fa-li"><?= view('icons/user') ?></span>
251                        <a href="<?= e(route(SiteRegistrationPage::class)) ?>">
252                            <?= I18N::translate('Sign-in and registration') ?>
253                        </a>
254                    </li>
255                </ul>
256            </div>
257            <div class="col-sm">
258                <ul class="fa-ul mx-0">
259                    <li>
260                        <span class="fa-li"><?= view('icons/list') ?></span>
261                        <a href="<?= e(route(SiteLogsPage::class)) ?>">
262                            <?= I18N::translate('Website logs') ?>
263                        </a>
264                    </li>
265                    <li>
266                        <span class="fa-li"><?= view('icons/delete') ?></span>
267                        <a href="<?= e(route(CleanDataFolder::class)) ?>">
268                            <?= I18N::translate('Clean up data folder') ?>
269                        </a>
270                    </li>
271                    <li>
272                        <span class="fa-li"><?= view('icons/server') ?></span>
273                        <a href="<?= e(route(PhpInformation::class)) ?>">
274                            <?= I18N::translate('Server information') ?>
275                        </a>
276                    </li>
277                </ul>
278            </div>
279            <div class="col-sm">
280                <ul class="fa-ul mx-0">
281                    <li>
282                        <span class="fa-li"><?= view('icons/tag') ?></span>
283                        <a href="<?= e(route(SiteTagsPage::class)) ?>">
284                            <?= I18N::translate('GEDCOM tags') ?>
285                        </a>
286                    </li>
287                </ul>
288            </div>
289        </div>
290    </div>
291</div>
292
293<!-- FAMILY TREES -->
294<div class="card mb-4 <?= array_sum($changes) ? 'card-outline-danger' : '' ?>">
295    <div class="card-header">
296        <h2 class="mb-0">
297            <?= I18N::translate('Family trees') ?>
298            <span class="badge bg-secondary">
299                    <?= I18N::number($all_trees->count()) ?>
300                </span>
301        </h2>
302    </div>
303
304    <div class="card-body">
305        <?php if ($all_trees->isEmpty()) : ?>
306            <p class="alert alert-info">
307                <?= I18N::translate('You need to create a family tree.') ?>
308            </p>
309        <?php endif ?>
310
311        <ul class="fa-ul mx-0">
312            <li>
313                <span class="fa-li"><?= view('icons/add') ?></span>
314                <a href="<?= e(route(CreateTreePage::class)) ?>">
315                    <?= I18N::translate('Create a family tree') ?>
316                </a>
317            </li>
318            <li>
319                <span class="fa-li"><?= view('icons/block') ?></span>
320                <a href="<?= e(route(TreePageDefaultEdit::class)) ?>">
321                    <?= I18N::translate('Set the default blocks for new family trees') ?>
322                </a>
323            </li>
324            <?php if (count($all_trees) > 1) : ?>
325                <li>
326                    <span class="fa-li"><?= view('icons/merge') ?></span>
327                    <a href="<?= e(route(MergeTreesPage::class)) ?>">
328                        <?= I18N::translate('Merge family trees') ?>
329                    </a>
330                </li>
331            <?php endif ?>
332        </ul>
333
334        <table class="table table-sm">
335            <caption class="visually-hidden">
336                <?= I18N::translate('Family trees') ?>
337            </caption>
338
339            <thead>
340                <tr>
341                    <th><?= I18N::translate('Family tree') ?></th>
342                    <th class="align-digits"><?= I18N::translate('Pending changes') ?></th>
343                    <th class="d-none d-sm-table-cell align-digits"><?= I18N::translate('Individuals') ?></th>
344                    <th class="d-none d-lg-table-cell align-digits"><?= I18N::translate('Families') ?></th>
345                    <th class="d-none d-sm-table-cell align-digits"><?= I18N::translate('Sources') ?></th>
346                    <th class="d-none d-lg-table-cell align-digits"><?= I18N::translate('Repositories') ?></th>
347                    <th class="d-none d-sm-table-cell align-digits"><?= I18N::translate('Media') ?></th>
348                    <th class="d-none d-lg-table-cell align-digits"><?= I18N::translate('Notes') ?></th>
349                    <th class="d-none d-lg-table-cell align-digits"><?= I18N::translate('Submitters') ?></th>
350                </tr>
351            </thead>
352
353            <tbody>
354                <?php foreach ($all_trees as $tree) : ?>
355                    <tr class="<?= $changes[$tree->id()] ? 'danger' : '' ?>">
356                        <th scope="row">
357                            <?= view('icons/preferences') ?>
358                            <a href="<?= e(route(ManageTrees::class, ['tree' => $tree->name()])) ?>">
359                                <?= e($tree->name()) ?>
360                                -
361                                <?= e($tree->title()) ?>
362                            </a>
363                        </th>
364                        <td style="text-align: right;">
365                            <?php if ($changes[$tree->id()]) : ?>
366                                <a href="<?= e(route(PendingChanges::class, ['tree' => $tree->name(), 'url' => route(ControlPanel::class)])) ?>">
367                                    <?= I18N::number($changes[$tree->id()]) ?>
368                                    <span class="visually-hidden"><?= I18N::translate('Pending changes') ?> <?= e($tree->title()) ?></span>
369                                </a>
370                            <?php else : ?>
371                                -
372                            <?php endif ?>
373                        </td>
374                        <td class="d-none d-sm-table-cell align-digits">
375                            <?php if ($individuals[$tree->id()] > 0) : ?>
376                                <?php if ($individual_list_module instanceof IndividualListModule) : ?>
377                                    <a href="<?= e($individual_list_module->listUrl($tree)) ?>"><?= I18N::number($individuals[$tree->id()]) ?></a>
378                                <?php else : ?>
379                                    <?= I18N::number($individuals[$tree->id()]) ?>
380                                <?php endif ?>
381                            <?php else : ?>
382                                -
383                            <?php endif ?>
384                        </td>
385                        <td class="d-none d-lg-table-cell align-digits">
386                            <?php if ($families[$tree->id()] > 0) : ?>
387                                <?php if ($family_list_module instanceof FamilyListModule) : ?>
388                                    <a href="<?= e($family_list_module->listUrl($tree)) ?>"><?= I18N::number($families[$tree->id()]) ?></a>
389                                <?php else : ?>
390                                    <?= I18N::number($families[$tree->id()]) ?>
391                                <?php endif ?>
392                            <?php else : ?>
393                                -
394                            <?php endif ?>
395                        </td>
396                        <td class="d-none d-sm-table-cell align-digits">
397                            <?php if ($sources[$tree->id()] > 0) : ?>
398                                <?php if ($source_list_module instanceof SourceListModule) : ?>
399                                    <a href="<?= e($source_list_module->listUrl($tree)) ?>"><?= I18N::number($sources[$tree->id()]) ?></a>
400                                <?php else : ?>
401                                    <?= I18N::number($sources[$tree->id()]) ?>
402                                <?php endif ?>
403                            <?php else : ?>
404                                -
405                            <?php endif ?>
406                        </td>
407                        <td class="d-none d-lg-table-cell align-digits">
408                            <?php if ($repositories[$tree->id()] > 0) : ?>
409                                <?php if ($repository_list_module instanceof RepositoryListModule) : ?>
410                                    <a href="<?= e($repository_list_module->listUrl($tree)) ?>"><?= I18N::number($repositories[$tree->id()]) ?></a>
411                                <?php else : ?>
412                                    <?= I18N::number($repositories[$tree->id()]) ?>
413                                <?php endif ?>
414                            <?php else : ?>
415                                -
416                            <?php endif ?>
417                        </td>
418                        <td class="d-none d-sm-table-cell align-digits">
419                            <?php if ($media[$tree->id()] > 0) : ?>
420                                <?php if ($media_list_module instanceof MediaListModule) : ?>
421                                    <a href="<?= e($media_list_module->listUrl($tree)) ?>"><?= I18N::number($media[$tree->id()]) ?></a>
422                                <?php else : ?>
423                                    <?= I18N::number($media[$tree->id()]) ?>
424                                <?php endif ?>
425                            <?php else : ?>
426                                -
427                            <?php endif ?>
428                        </td>
429                        <td class="d-none d-lg-table-cell align-digits">
430                            <?php if ($notes[$tree->id()] > 0) : ?>
431                                <?php if ($note_list_module instanceof NoteListModule) : ?>
432                                    <a href="<?= e($note_list_module->listUrl($tree)) ?>"><?= I18N::number($notes[$tree->id()]) ?></a>
433                                <?php else : ?>
434                                    <?= I18N::number($notes[$tree->id()]) ?>
435                                <?php endif ?>
436                            <?php else : ?>
437                                -
438                            <?php endif ?>
439                        </td>
440                         <td class="d-none d-lg-table-cell align-digits">
441                            <?php if ($submitters[$tree->id()] > 0) : ?>
442                                <?php if ($submitter_list_module instanceof SubmitterListModule) : ?>
443                                    <a href="<?= e($submitter_list_module->listUrl($tree)) ?>"><?= I18N::number($submitters[$tree->id()]) ?></a>
444                                <?php else : ?>
445                                    <?= I18N::number($submitters[$tree->id()]) ?>
446                                <?php endif ?>
447                            <?php else : ?>
448                                -
449                            <?php endif ?>
450                        </td>
451                    </tr>
452                <?php endforeach ?>
453            </tbody>
454
455            <tfoot>
456                <tr>
457                    <th scope="row">
458                        <?= I18N::translate('Total') ?>
459                    </th>
460                    <td class="align-digits">
461                        <?= I18N::number(array_sum($changes)) ?>
462                    </td>
463                    <td class="d-none d-sm-table-cell align-digits">
464                        <?= I18N::number($individuals->sum()) ?>
465                    </td>
466                    <td class="d-none d-lg-table-cell align-digits">
467                        <?= I18N::number($families->sum()) ?>
468                    </td>
469                    <td class="d-none d-sm-table-cell align-digits">
470                        <?= I18N::number($sources->sum()) ?>
471                    </td>
472                    <td class="d-none d-lg-table-cell align-digits">
473                        <?= I18N::number($repositories->sum()) ?>
474                    </td>
475                    <td class="d-none d-sm-table-cell align-digits">
476                        <?= I18N::number($media->sum()) ?>
477                    </td>
478                    <td class="d-none d-lg-table-cell align-digits">
479                        <?= I18N::number($notes->sum()) ?>
480                    </td>
481                    <td class="d-none d-lg-table-cell align-digits">
482                        <?= I18N::number($submitters->sum()) ?>
483                    </td>
484                </tr>
485            </tfoot>
486        </table>
487    </div>
488</div>
489
490<?php if ($show_synchronize) : ?>
491    <?= view('admin/synchronize-trees') ?>
492<?php endif ?>
493
494<!-- USERS -->
495<div class="card mb-4 <?= $unapproved || $unverified ? 'card-outline-danger' : '' ?>">
496    <div class="card-header">
497        <h2 class="mb-0">
498            <?= I18N::translate('Users') ?>
499            <span class="badge bg-secondary">
500                    <?= I18N::number(count($all_users)) ?>
501                </span>
502        </h2>
503    </div>
504    <div class="card-body">
505        <dl class="row">
506            <?php foreach ([I18N::translate('Administrators') => $administrators, I18N::translate('Managers') => $managers, I18N::translate('Moderators') => $moderators, I18N::translate('Not verified by the user') => $unverified, I18N::translate('Not approved by an administrator') => $unapproved] as $label => $list) : ?>
507                <?php if (!empty($list)) : ?>
508                    <dt class="col-sm-6">
509                        <?= $label ?>
510                    </dt>
511                    <dd class="col-sm-6">
512                        <?php foreach ($list as $n => $user) : ?>
513                            <?= $n ? I18N::$list_separator : '' ?>
514                            <a href="<?= e(route(UserEditPage::class, ['user_id' => $user->id()])) ?>" dir="auto">
515                                <?= e($user->realName()) ?>
516                            </a>
517                        <?php endforeach ?>
518                        <?php if ($list->isEmpty()) : ?>
519520                        <?php endif ?>
521                    </dd>
522                <?php endif ?>
523            <?php endforeach ?>
524        </dl>
525
526        <div class="row">
527            <div class="col-sm-6">
528                <ul class="fa-ul mx-0">
529                    <li>
530                        <span class="fa-li"><?= view('icons/preferences') ?></span>
531                        <a href="<?= e(route(UserListPage::class)) ?>">
532                            <?= I18N::translate('User administration') ?>
533                        </a>
534                    </li>
535                    <li>
536                        <span class="fa-li"><?= view('icons/add') ?></span>
537                        <a href="<?= e(route(UserAddPage::class)) ?>">
538                            <?= I18N::translate('Add a user') ?>
539                        </a>
540                    </li>
541                    <li>
542                        <span class="fa-li"><?= view('icons/delete') ?></span>
543                        <a href="<?= e(route(UsersCleanupPage::class)) ?>">
544                            <?= I18N::translate('Delete inactive users') ?>
545                        </a>
546                    </li>
547                    <li>
548                        <span class="fa-li"><?= view('icons/block') ?></span>
549                        <a href="<?= e(route(UserPageDefaultEdit::class)) ?>">
550                            <?= I18N::translate('Set the default blocks for new users') ?>
551                        </a>
552                    </li>
553                </ul>
554            </div>
555            <div class="col-sm-6">
556                <ul class="fa-ul mx-0">
557                    <?php foreach ($recipients as $to => $recipient) : ?>
558                    <li>
559                        <span class="fa-li"><?= view('icons/email') ?></span>
560                        <a href="<?= e(route(BroadcastPage::class, ['to' => $to])) ?>">
561                            <?= $recipient ?>
562                        </a>
563                    </li>
564                    <?php endforeach ?>
565                </ul>
566            </div>
567        </div>
568    </div>
569</div>
570
571<!-- MODULES -->
572<div class="card mb-4">
573    <div class="card-header">
574        <h2 class="mb-0">
575            <?= I18N::translate('Modules') ?>
576            <?= view('components/badge', ['count' => $all_modules_enabled->count(), 'total' => $all_modules_disabled->count(), 'context' => 'secondary']) ?>
577        </h2>
578    </div>
579
580    <div class="card-body">
581        <ul class="fa-ul mx-0">
582            <li>
583                <span class="fa-li"><?= view('icons/module') ?></span>
584                <a href="<?= e(route(ModulesAllPage::class)) ?>">
585                    <?= I18N::translate('All modules') ?>
586                </a>
587                <?= view('components/badge', ['count' => $all_modules_enabled->count(), 'total' => $all_modules_disabled->count(), 'context' => 'primary']) ?>
588            </li>
589        </ul>
590
591        <div class="row">
592            <div class="col-sm-4">
593                <h3>
594                    <?= I18N::translate('Website') ?>
595                </h3>
596
597                <ul class="fa-ul mx-0">
598                    <li>
599                        <span class="fa-li"><?= view('icons/footer') ?></span>
600                        <a href="<?= e(route(ModulesFootersPage::class)) ?>">
601                            <?= I18N::translate('Footers') ?>
602                        </a>
603                        <?= view('components/badge', ['count' => $footer_modules_enabled->count(), 'total' => $footer_modules_disabled->count(), 'context' => 'primary']) ?>
604                    </li>
605                    <li>
606                        <span class="fa-li"><?= view('icons/analytics') ?></span>
607                        <a href="<?= e(route(ModulesAnalyticsPage::class)) ?>">
608                            <?= I18N::translate('Tracking and analytics') ?>
609                        </a>
610                        <?= view('components/badge', ['count' => $analytics_modules_enabled->count(), 'total' => $analytics_modules_disabled->count(), 'context' => 'primary']) ?>
611                    </li>
612                    <li>
613                        <span class="fa-li"><?= view('icons/theme') ?></span>
614                        <a href="<?= e(route(ModulesThemesPage::class)) ?>">
615                            <?= I18N::translate('Themes') ?>
616                        </a>
617                        <?= view('components/badge', ['count' => $theme_modules_enabled->count(), 'total' => $theme_modules_disabled->count(), 'context' => 'primary']) ?>
618                    </li>
619                    <li>
620                        <span class="fa-li"><?= view('icons/language') ?></span>
621                        <a href="<?= e(route(ModulesLanguagesPage::class)) ?>">
622                            <?= I18N::translate('Languages') ?>
623                        </a>
624                        <?= view('components/badge', ['count' => $language_modules_enabled->count(), 'total' => $language_modules_disabled->count(), 'context' => 'primary']) ?>
625                    </li>
626                </ul>
627
628                <h3>
629                    <?= I18N::translate('Home page') ?>
630                </h3>
631
632                <ul class="fa-ul mx-0">
633                    <li>
634                        <span class="fa-li"><?= view('icons/block') ?></span>
635                        <a href="<?= e(route(ModulesBlocksPage::class)) ?>">
636                            <?= I18N::translate('Blocks') ?>
637                        </a>
638                        <?= view('components/badge', ['count' => $block_modules_enabled->count(), 'total' => $block_modules_disabled->count(), 'context' => 'primary']) ?>
639                    </li>
640                </ul>
641
642                <h3>
643                    <?= I18N::translate('Geographic data') ?>
644                </h3>
645
646                <ul class="fa-ul mx-0">
647                    <li>
648                        <span class="fa-li"><?= view('icons/search') ?></span>
649                        <a href="<?= e(route(ModulesMapAutocompletePage::class)) ?>">
650                            <?= /* I18N: Automatic suggestions when you type */ I18N::translate('Autocomplete') ?>
651                        </a>
652                        <?= view('components/badge', ['count' => $map_autocomplete_modules_enabled->count(), 'total' => $map_autocomplete_modules_disabled->count(), 'context' => 'primary']) ?>
653                    </li>
654                    <li>
655                        <span class="fa-li"><?= view('icons/search-location') ?></span>
656                        <a href="<?= e(route(ModulesMapGeoLocationsPage::class)) ?>">
657                            <?= /* I18N: find latitude/longitude for a place */ I18N::translate('Geolocation') ?>
658                        </a>
659                        <?= view('components/badge', ['count' => $map_search_modules_enabled->count(), 'total' => $map_search_modules_disabled->count(), 'context' => 'primary']) ?>
660                    </li>
661                    <li>
662                        <span class="fa-li"><?= view('icons/location') ?></span>
663                        <a href="<?= e(route(ModulesMapLinksPage::class)) ?>">
664                            <?= /* I18N: Links to maps */ I18N::translate('Map links') ?>
665                        </a>
666                        <?= view('components/badge', ['count' => $map_link_modules_enabled->count(), 'total' => $map_link_modules_disabled->count(), 'context' => 'primary']) ?>
667                    </li>
668                    <li>
669                        <span class="fa-li"><?= view('icons/map') ?></span>
670                        <a href="<?= e(route(ModulesMapProvidersPage::class)) ?>">
671                            <?= I18N::translate('Map providers') ?>
672                        </a>
673                        <?= view('components/badge', ['count' => $map_provider_modules_enabled->count(), 'total' => $map_provider_modules_disabled->count(), 'context' => 'primary']) ?>
674                    </li>
675                </ul>
676            </div>
677
678            <div class="col-sm-4">
679                <h3>
680                    <?= I18N::translate('Genealogy') ?>
681                </h3>
682
683                <ul class="fa-ul mx-0">
684                    <li>
685                        <span class="fa-li"><?= view('icons/menu') ?></span>
686                        <a href="<?= e(route(ModulesMenusPage::class)) ?>">
687                            <?= I18N::translate('Menus') ?>
688                        </a>
689                        <?= view('components/badge', ['count' => $menu_modules_enabled->count(), 'total' => $menu_modules_disabled->count(), 'context' => 'primary']) ?>
690                    </li>
691                    <li>
692                        <span class="fa-li"><?= view('icons/chart') ?></span>
693                        <a href="<?= e(route(ModulesChartsPage::class)) ?>">
694                            <?= I18N::translate('Charts') ?>
695                        </a>
696                        <?= view('components/badge', ['count' => $chart_modules_enabled->count(), 'total' => $chart_modules_disabled->count(), 'context' => 'primary']) ?>
697                    </li>
698                    <li>
699                        <span class="fa-li"><?= view('icons/list') ?></span>
700                        <a href="<?= e(route(ModulesListsPage::class)) ?>">
701                            <?= I18N::translate('Lists') ?>
702                        </a>
703                        <?= view('components/badge', ['count' => $list_modules_enabled->count(), 'total' => $list_modules_disabled->count(), 'context' => 'primary']) ?>
704                    </li>
705                    <li>
706                        <span class="fa-li"><?= view('icons/report') ?></span>
707                        <a href="<?= e(route(ModulesReportsPage::class)) ?>">
708                            <?= I18N::translate('Reports') ?>
709                        </a>
710                        <?= view('components/badge', ['count' => $report_modules_enabled->count(), 'total' => $report_modules_disabled->count(), 'context' => 'primary']) ?>
711                    </li>
712                    <li>
713                        <span class="fa-li"><?= view('icons/data-fix') ?></span>
714                        <a href="<?= e(route(ModulesDataFixesPage::class)) ?>">
715                            <?= I18N::translate('Data fixes') ?>
716                        </a>
717                        <?= view('components/badge', ['count' => $data_fix_modules_enabled->count(), 'total' => $data_fix_modules_disabled->count(), 'context' => 'primary']) ?>
718                    </li>
719                </ul>
720
721                <h3>
722                    <?= I18N::translate('Individual page') ?>
723                </h3>
724
725                <ul class="fa-ul mx-0">
726                    <li>
727                        <span class="fa-li"><?= view('icons/tab') ?></span>
728                        <a href="<?= e(route(ModulesTabsPage::class)) ?>">
729                            <?= I18N::translate('Tabs') ?>
730                            <?= view('components/badge', ['count' => $tab_modules_enabled->count(), 'total' => $tab_modules_disabled->count(), 'context' => 'primary']) ?>
731                        </a>
732                    </li>
733                    <li>
734                        <span class="fa-li"><?= view('icons/sidebar') ?></span>
735                        <a href="<?= e(route(ModulesSidebarsPage::class)) ?>">
736                            <?= I18N::translate('Sidebars') ?>
737                        </a>
738                        <?= view('components/badge', ['count' => $sidebar_modules_enabled->count(), 'total' => $sidebar_modules_disabled->count(), 'context' => 'primary']) ?>
739                    </li>
740                    <li>
741                        <span class="fa-li"><?= view('icons/history') ?></span>
742                        <a href="<?= e(route(ModulesHistoricEventsPage::class)) ?>">
743                            <?= I18N::translate('Historic events') ?>
744                        </a>
745                        <?= view('components/badge', ['count' => $history_modules_enabled->count(), 'total' => $history_modules_disabled->count(), 'context' => 'primary']) ?>
746                    </li>
747                    <li>
748                        <span class="fa-li"><?= view('icons/share') ?></span>
749                        <a href="<?= e(route(ModulesSharesPage::class)) ?>">
750                            <?= /* I18N: plural noun - things that can be shared */  I18N::translate('Shares') ?>
751                        </a>
752                        <?= view('components/badge', ['count' => $share_modules_enabled->count(), 'total' => $share_modules_disabled->count(), 'context' => 'primary']) ?>
753                    </li>
754                </ul>
755            </div>
756
757            <div class="col-sm-4">
758
759                <h3>
760                    <?= I18N::translate('Other') ?>
761                </h3>
762
763                <ul class="fa-ul mx-0">
764                    <?php foreach ($other_modules as $module) : ?>
765                        <li>
766                            <?php if ($module->isEnabled()) : ?>
767                                <span class="fa-li"><?= view('icons/module') ?></span>
768                                <?php if ($module instanceof ModuleConfigInterface) : ?>
769                                    <a href="<?= e($module->getConfigLink()) ?>">
770                                    <?= $module->title() ?>
771                                    <?= view('icons/preferences') ?>
772                                    </a>
773                                <?php else : ?>
774                                    <?= $module->title() ?>
775                                <?php endif ?>
776                            <?php else : ?>
777                                <span class="text-muted">
778                                    <span class="fa-li"><?= view('icons/module') ?></span>
779                                    <?= $module->title() ?>
780                                    <?php if ($module instanceof ModuleConfigInterface) : ?>
781                                        <?= view('icons/preferences') ?>
782                                    <?php endif ?>
783                                </span>
784                            <?php endif ?>
785
786                            <?php if ($module instanceof ModuleCustomInterface) : ?>
787                                <?= view('admin/custom-module-info', ['module' => $module]) ?>
788                            <?php endif ?>
789                        </li>
790                    <?php endforeach ?>
791                </ul>
792            </div>
793        </div>
794    </div>
795</div>
796
797<!-- MEDIA -->
798<div class="card mb-4">
799    <div class="card-header">
800        <h2 class="mb-0">
801            <?= I18N::translate('Media') ?>
802        </h2>
803    </div>
804    <div class="card-body">
805        <ul class="fa-ul mx-0">
806            <li>
807                <span class="fa-li"><?= view('icons/preferences') ?></span>
808                <a href="<?= e(route(ManageMediaPage::class)) ?>">
809                    <?= I18N::translate('Manage media') ?>
810                </a>
811            </li>
812            <li>
813                <span class="fa-li"><?= view('icons/upload') ?></span>
814                <a href="<?= e(route(UploadMediaPage::class)) ?>">
815                    <?= I18N::translate('Upload media files') ?>
816                </a>
817            </li>
818            <li>
819                <span class="fa-li"><?= view('icons/link') ?></span>
820                <a href="<?= e(route(FixLevel0MediaPage::class)) ?>">
821                    <?= I18N::translate('Link media objects to facts and events') ?>
822                </a>
823            </li>
824            <li>
825                <span class="fa-li"><?= view('icons/upload') ?></span>
826                <a href="<?= e(route(ImportThumbnailsPage::class)) ?>">
827                    <?= I18N::translate('Import custom thumbnails from webtrees version 1') ?>
828                </a>
829            </li>
830        </ul>
831    </div>
832</div>
833
834<!-- MAPS -->
835<div class="card mb-4">
836    <div class="card-header">
837        <h2 class="mb-0">
838            <?= I18N::translate('Map') ?>
839        </h2>
840    </div>
841    <div class="card-body">
842        <div class="row">
843            <div class="col-sm-12">
844                <ul class="fa-ul mx-0">
845                    <li>
846                        <span class="fa-li"><?= view('icons/location') ?></span>
847                        <a href="<?= e(route(MapDataList::class)) ?>">
848                            <?= I18N::translate('Geographic data') ?>
849                        </a>
850                    </li>
851                </ul>
852            </div>
853        </div>
854    </div>
855</div>
856
857<!-- OLD FILES -->
858<?php if (!empty($files_to_delete)) : ?>
859    <div class="card mb-4 card-outline-danger">
860        <div class="card-header">
861            <h2 class="mb-0">
862                <?= I18N::translate('Old files found') ?>
863            </h2>
864        </div>
865        <div class="card-body">
866            <p>
867                <?= I18N::translate('Files have been found from a previous version of webtrees. Old files can sometimes be a security risk. You should delete them.') ?>
868            </p>
869            <ul class="list-unstyled">
870                <?php foreach ($files_to_delete as $file_to_delete) : ?>
871                    <li dir="ltr">
872                        <code><?= e($file_to_delete) ?></code>
873                    </li>
874                <?php endforeach ?>
875            </ul>
876        </div>
877    </div>
878<?php endif ?>
879