Passed
Push — 2.1 ( 438dd8...4d644f )
by Greg
15:45 queued 07:28
created

Statistics::serverTime24()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2025 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
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees;
21
22
use Fisharebest\Webtrees\Contracts\UserInterface;
23
use Fisharebest\Webtrees\Module\ModuleBlockInterface;
24
use Fisharebest\Webtrees\Module\ModuleInterface;
25
use Fisharebest\Webtrees\Services\ModuleService;
26
use Fisharebest\Webtrees\Services\UserService;
27
use Fisharebest\Webtrees\SurnameTradition\PolishSurnameTradition;
28
use Illuminate\Database\Capsule\Manager as DB;
29
use Illuminate\Database\Query\Expression;
30
use Illuminate\Database\Query\JoinClause;
31
use Illuminate\Support\Collection;
32
use InvalidArgumentException;
33
use Psr\Http\Message\ServerRequestInterface;
34
use ReflectionClass;
35
use ReflectionException;
36
use ReflectionMethod;
37
use ReflectionNamedType;
38
39
use function app;
40
use function array_merge;
41
use function array_keys;
42
use function array_shift;
43
use function array_sum;
44
use function count;
45
use function e;
46
use function htmlspecialchars_decode;
47
use function implode;
48
use function in_array;
49
use function preg_replace;
50
use function round;
51
use function strip_tags;
52
use function strpos;
53
use function substr;
54
use function view;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Fisharebest\Webtrees\view. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
55
56
/**
57
 * A selection of pre-formatted statistical queries.
58
 * These are primarily used for embedded keywords on HTML blocks, but
59
 * are also used elsewhere in the code.
60
 */
61
class Statistics
62
{
63
    private Tree $tree;
64
65
    private ModuleService $module_service;
66
67
    private UserService $user_service;
68
69
    private StatisticsData $data;
70
71
    private StatisticsFormat $format;
72
73
    public function __construct(
74
        ModuleService $module_service,
75
        Tree $tree,
76
        UserService $user_service
77
    ) {
78
        $this->module_service = $module_service;
79
        $this->user_service   = $user_service;
80
        $this->tree           = $tree;
81
        $this->data           = new StatisticsData($tree, $user_service);
82
        $this->format         = new StatisticsFormat();
83
    }
84
85
    public function ageBetweenSpousesFM(string $limit = '10'): string
86
    {
87
        return $this->data->ageBetweenSpousesFM((int) $limit);
88
    }
89
90
    public function ageBetweenSpousesFMList(string $limit = '10'): string
91
    {
92
        return $this->data->ageBetweenSpousesFMList((int) $limit);
93
    }
94
95
    public function ageBetweenSpousesMF(string $limit = '10'): string
96
    {
97
        return $this->data->ageBetweenSpousesMF((int) $limit);
98
    }
99
100
    public function ageBetweenSpousesMFList(string $limit = '10'): string
101
    {
102
        return $this->data->ageBetweenSpousesMFList((int) $limit);
103
    }
104
105
    public function averageChildren(): string
106
    {
107
        return I18N::number($this->data->averageChildrenPerFamily(), 2);
108
    }
109
110
    public function averageLifespan(string $show_years = '0'): string
111
    {
112
        $days = $this->data->averageLifespanDays('ALL');
113
114
        return $show_years ? $this->format->age($days) : I18N::number((int) ($days / 365.25));
115
    }
116
117
    public function averageLifespanFemale(string $show_years = '0'): string
118
    {
119
        $days = $this->data->averageLifespanDays('F');
120
121
        return $show_years ? $this->format->age($days) : I18N::number((int) ($days / 365.25));
122
    }
123
124
    public function averageLifespanMale(string $show_years = '0'): string
125
    {
126
        $days = $this->data->averageLifespanDays('M');
127
128
        return $show_years ? $this->format->age($days) : I18N::number((int) ($days / 365.25));
129
    }
130
131
    public function browserDate(): string
132
    {
133
        return Registry::timestampFactory()->now()->format(strtr(I18N::dateFormat(), ['%' => '']));
134
    }
135
136
    public function browserTime(): string
137
    {
138
        return Registry::timestampFactory()->now()->format(strtr(I18N::timeFormat(), ['%' => '']));
139
    }
140
141
    public function browserTimezone(): string
142
    {
143
        return Registry::timestampFactory()->now()->format('T');
144
    }
145
146
    /**
147
     * Create any of the other blocks.
148
     * Use as #callBlock:block_name#
149
     *
150
     * @param string ...$params
151
     */
152
    public function callBlock(string $block = '', ...$params): string
153
    {
154
        $module = $this->module_service
155
            ->findByComponent(ModuleBlockInterface::class, $this->tree, Auth::user())
156
            ->first(static fn (ModuleInterface $module): bool => $module->name() === $block && $module->name() !== 'html');
157
158
        if ($module === null) {
159
            return '';
160
        }
161
162
        // Build the config array
163
        $cfg = [];
164
        foreach ($params as $config) {
165
            $bits = explode('=', $config);
166
167
            if (count($bits) < 2) {
168
                continue;
169
            }
170
171
            $v       = array_shift($bits);
172
            $cfg[$v] = implode('=', $bits);
173
        }
174
175
        return $module->getBlock($this->tree, 0, ModuleBlockInterface::CONTEXT_EMBED, $cfg);
176
    }
177
178
    public function chartCommonGiven(string $color1 = 'ffffff', string $color2 = '84beff', string $limit = '7'): string
179
    {
180
        $given = $this->data->commonGivenNames('ALL', 1, (int) $limit)->all();
181
182
        if ($given === []) {
183
            return I18N::translate('This information is not available.');
184
        }
185
186
        $tot = 0;
187
        foreach ($given as $count) {
188
            $tot += $count;
189
        }
190
191
        $data = [
192
            [
193
                I18N::translate('Name'),
194
                I18N::translate('Total'),
195
            ],
196
        ];
197
198
        foreach ($given as $name => $count) {
199
            $data[] = [$name, $count];
200
        }
201
202
        $count_all_names = $this->data->commonGivenNames('ALL', 1, PHP_INT_MAX)->sum();
203
204
        $data[] = [
205
            I18N::translate('Other'),
206
            $count_all_names - $tot,
207
        ];
208
209
        $colors = $this->format->interpolateRgb($color1, $color2, count($data) - 1);
210
211
        return view('statistics/other/charts/pie', [
212
            'title'    => null,
213
            'data'     => $data,
214
            'colors'   => $colors,
215
            'language' => I18N::languageTag(),
216
        ]);
217
    }
218
219
    public function chartCommonSurnames(
220
        string $color1 = 'ffffff',
221
        string $color2 = '84beff',
222
        string $limit = '10'
223
    ): string {
224
        $all_surnames = $this->data->commonSurnames((int) $limit, 0, 'count');
225
226
        if ($all_surnames === []) {
227
            return I18N::translate('This information is not available.');
228
        }
229
230
        $surname_tradition = Registry::surnameTraditionFactory()
231
            ->make($this->tree->getPreference('SURNAME_TRADITION'));
232
233
        $tot = 0;
234
        foreach ($all_surnames as $surnames) {
235
            $tot += array_sum($surnames);
236
        }
237
238
        $data = [
239
            [
240
                I18N::translate('Name'),
241
                I18N::translate('Total')
242
            ],
243
        ];
244
245
        foreach ($all_surnames as $surns) {
246
            $max_name  = 0;
247
            $count_per = 0;
248
            $top_name  = '';
249
250
            foreach ($surns as $spfxsurn => $count) {
251
                $per = $count;
252
                $count_per += $per;
253
254
                // select most common surname from all variants
255
                if ($per > $max_name) {
256
                    $max_name = $per;
257
                    $top_name = $spfxsurn;
258
                }
259
            }
260
261
            if ($surname_tradition instanceof PolishSurnameTradition) {
262
                // Most common surname should be in male variant (Kowalski, not Kowalska)
263
                $top_name = preg_replace(
264
                    [
265
                        '/ska$/',
266
                        '/cka$/',
267
                        '/dzka$/',
268
                        '/żka$/',
269
                    ],
270
                    [
271
                        'ski',
272
                        'cki',
273
                        'dzki',
274
                        'żki',
275
                    ],
276
                    $top_name
277
                );
278
            }
279
280
            $data[] = [(string) $top_name, $count_per];
281
        }
282
283
        $data[] = [
284
            I18N::translate('Other'),
285
            $this->data->countIndividuals() - $tot
286
        ];
287
288
        $colors = $this->format->interpolateRgb($color1, $color2, count($data) - 1);
289
290
        return view('statistics/other/charts/pie', [
291
            'title'    => null,
292
            'data'     => $data,
293
            'colors'   => $colors,
294
            'language' => I18N::languageTag(),
295
        ]);
296
    }
297
298
    public function chartDistribution(
299
        string $chart_shows = 'world',
300
        string $chart_type = '',
301
        string $surname = ''
302
    ): string {
303
        return $this->data->chartDistribution($chart_shows, $chart_type, $surname);
304
    }
305
306
    public function chartFamsWithSources(string $color1 = 'c2dfff', string $color2 = '84beff'): string
307
    {
308
        $total_families              = $this->data->countFamilies();
309
        $total_families_with_sources = $this->data->countFamiliesWithSources();
310
311
        $data = [
312
            [I18N::translate('Without sources'), $total_families - $total_families_with_sources],
313
            [I18N::translate('With sources'), $total_families_with_sources],
314
        ];
315
316
        return $this->format->pieChart(
317
            $data,
318
            [$color1, $color2],
319
            I18N::translate('Families with sources'),
320
            I18N::translate('Type'),
321
            I18N::translate('Total'),
322
            true
323
        );
324
    }
325
326
    public function chartIndisWithSources(string $color1 = 'c2dfff', string $color2 = '84beff'): string
327
    {
328
        $total_individuals              = $this->data->countIndividuals();
329
        $total_individuals_with_sources = $this->data->countIndividualsWithSources();
330
331
        $data = [
332
            [I18N::translate('Without sources'), $total_individuals - $total_individuals_with_sources],
333
            [I18N::translate('With sources'), $total_individuals_with_sources],
334
        ];
335
336
        return $this->format->pieChart(
337
            $data,
338
            [$color1, $color2],
339
            I18N::translate('Individuals with sources'),
340
            I18N::translate('Type'),
341
            I18N::translate('Total'),
342
            true
343
        );
344
    }
345
346
    public function chartLargestFamilies(
347
        string $color1 = 'ffffff',
348
        string $color2 = '84beff',
349
        string $limit = '7'
350
    ): string {
351
        $data = DB::table('families')
352
            ->select(['f_numchil AS total', 'f_id AS id'])
353
            ->where('f_file', '=', $this->tree->id())
354
            ->orderBy('total', 'desc')
355
            ->limit((int) $limit)
356
            ->get()
357
            ->map(fn (object $row): array => [
358
                htmlspecialchars_decode(strip_tags(Registry::familyFactory()->make($row->id, $this->tree)->fullName())),
359
                (int) $row->total,
360
            ])
361
            ->all();
362
363
        return $this->format->pieChart(
364
            $data,
365
            $this->format->interpolateRgb($color1, $color2, count($data)),
366
            I18N::translate('Largest families'),
367
            I18N::translate('Family'),
368
            I18N::translate('Children')
369
        );
370
    }
371
372
    public function chartMedia(string $color1 = 'ffffff', string $color2 = '84beff'): string
373
    {
374
        $data = $this->data->countMediaByType();
375
376
        return $this->format->pieChart(
377
            $data,
378
            $this->format->interpolateRgb($color1, $color2, count($data)),
379
            I18N::translate('Media by type'),
380
            I18N::translate('Type'),
381
            I18N::translate('Total'),
382
        );
383
    }
384
385
    public function chartMortality(string $color_living = '#ffffff', string $color_dead = '#cccccc'): string
386
    {
387
        $total_living = $this->data->countIndividualsLiving();
388
        $total_dead   = $this->data->countIndividualsDeceased();
389
390
        $data = [
391
            [I18N::translate('Century'), I18N::translate('Total')],
392
        ];
393
394
        if ($total_living > 0 || $total_dead > 0) {
395
            $data[] = [I18N::translate('Living'), $total_living];
396
            $data[] = [I18N::translate('Dead'), $total_dead];
397
        }
398
399
        $colors = $this->format->interpolateRgb($color_living, $color_dead, count($data) - 1);
400
401
        return view('statistics/other/charts/pie', [
402
            'title'            => null,
403
            'data'             => $data,
404
            'colors'           => $colors,
405
            'labeledValueText' => 'percentage',
406
            'language'         => I18N::languageTag(),
407
        ]);
408
    }
409
410
    public function chartNoChildrenFamilies(): string
411
    {
412
        $data = [
413
            [
414
                I18N::translate('Century'),
415
                I18N::translate('Total'),
416
            ],
417
        ];
418
419
        $records = DB::table('families')
420
            ->selectRaw('ROUND((d_year + 49) / 100, 0) AS century')
421
            ->selectRaw('COUNT(*) AS total')
422
            ->join('dates', static function (JoinClause $join): void {
423
                $join->on('d_file', '=', 'f_file')
424
                    ->on('d_gid', '=', 'f_id');
425
            })
426
            ->where('f_file', '=', $this->tree->id())
427
            ->where('f_numchil', '=', 0)
428
            ->where('d_fact', '=', 'MARR')
429
            ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
430
            ->groupBy(['century'])
431
            ->orderBy('century')
432
            ->get()
433
            ->map(static fn (object $row): object => (object) [
434
                'century' => (int) $row->century,
435
                'total'   => (int) $row->total,
436
            ])
437
            ->all();
438
439
        $total = 0;
440
441
        foreach ($records as $record) {
442
            $total += $record->total;
443
444
            $data[] = [
445
                $this->format->century($record->century),
446
                $record->total,
447
            ];
448
        }
449
450
        $families_with_no_children = $this->data->countFamiliesWithNoChildren();
451
452
        if ($families_with_no_children - $total > 0) {
453
            $data[] = [
454
                I18N::translateContext('unknown century', 'Unknown'),
455
                $families_with_no_children - $total,
456
            ];
457
        }
458
459
        $chart_title   = I18N::translate('Number of families without children');
460
        $chart_options = [
461
            'title'    => $chart_title,
462
            'subtitle' => '',
463
            'legend'   => [
464
                'position' => 'none',
465
            ],
466
            'vAxis'    => [
467
                'title' => I18N::translate('Total families'),
468
            ],
469
            'hAxis'    => [
470
                'showTextEvery' => 1,
471
                'slantedText'   => false,
472
                'title'         => I18N::translate('Century'),
473
            ],
474
            'colors'   => [
475
                '#84beff',
476
            ],
477
        ];
478
479
        return view('statistics/other/charts/column', [
480
            'data'          => $data,
481
            'chart_options' => $chart_options,
482
            'chart_title'   => $chart_title,
483
            'language'      => I18N::languageTag(),
484
        ]);
485
    }
486
487
    public function chartSex(
488
        string $color_female = '#ffd1dc',
489
        string $color_male = '#84beff',
490
        string $color_unknown = '#777777',
491
        string $color_other = '#777777'
492
    ): string {
493
        $data = [
494
            [I18N::translate('Males'), $this->data->countIndividualsBySex('M')],
495
            [I18N::translate('Females'), $this->data->countIndividualsBySex('F')],
496
            [I18N::translate('Unknown'), $this->data->countIndividualsBySex('U')],
497
            [I18N::translate('Other'), $this->data->countIndividualsBySex('X')],
498
        ];
499
500
        return $this->format->pieChart(
501
            $data,
502
            [$color_male, $color_female, $color_unknown, $color_other],
503
            I18N::translate('Sex'),
504
            I18N::translate('Sex'),
505
            I18N::translate('Total'),
506
            true
507
        );
508
    }
509
510
    public function commonBirthPlacesList(string $limit = '10'): string
511
    {
512
        return view('statistics/other/top10-list', ['records' => $this->data->countPlacesForIndividuals('BIRT', (int) $limit)]);
513
    }
514
515
    public function commonCountriesList(string $limit = '10'): string
516
    {
517
        return view('statistics/other/top10-list', ['records' => $this->data->countCountries((int) $limit)]);
518
    }
519
520
    public function commonDeathPlacesList(string $limit = '10'): string
521
    {
522
        return view('statistics/other/top10-list', ['records' => $this->data->countPlacesForIndividuals('DEAT', (int) $limit)]);
523
    }
524
525
    public function commonGiven(string $threshold = '1', string $limit = '10'): string
526
    {
527
        return $this->data->commonGivenNames('ALL', (int) $threshold, (int) $limit)
528
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
529
                $key => '<bdi>' . e($key) . '</bdi>',
530
            ])
531
            ->implode(I18N::$list_separator);
532
    }
533
534
    public function commonGivenFemale(string $threshold = '1', string $limit = '10'): string
535
    {
536
        return $this->data->commonGivenNames('F', (int) $threshold, (int) $limit)
537
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
538
                $key => '<bdi>' . e($key) . '</bdi>',
539
            ])
540
            ->implode(I18N::$list_separator);
541
    }
542
543
    public function commonGivenFemaleList(string $threshold = '1', string $limit = '10'): string
544
    {
545
        return view('lists/given-names-list', [
546
            'given_names' => $this->data->commonGivenNames('F', (int) $threshold, (int) $limit)->all(),
547
            'show_totals' => false,
548
        ]);
549
    }
550
551
    public function commonGivenFemaleListTotals(string $threshold = '1', string $limit = '10'): string
552
    {
553
        return view('lists/given-names-list', [
554
            'given_names' => $this->data->commonGivenNames('F', (int) $threshold, (int) $limit)->all(),
555
            'show_totals' => true,
556
        ]);
557
    }
558
559
    public function commonGivenFemaleTable(string $threshold = '1', string $limit = '10'): string
560
    {
561
        return view('lists/given-names-table', [
562
            'given_names' => $this->data->commonGivenNames('F', (int) $threshold, (int) $limit)->all(),
563
            'order'       => [[1, 'desc']],
564
        ]);
565
    }
566
567
    public function commonGivenFemaleTotals(string $threshold = '1', string $limit = '10'): string
568
    {
569
        return $this->data->commonGivenNames('F', (int) $threshold, (int) $limit)
570
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
571
                $key => '<bdi>' . e($key) . '</bdi> (' . I18N::number($value) . ')',
572
            ])
573
            ->implode(I18N::$list_separator);
574
    }
575
576
    public function commonGivenList(string $threshold = '1', string $limit = '10'): string
577
    {
578
        return view('lists/given-names-list', [
579
            'given_names' => $this->data->commonGivenNames('ALL', (int) $threshold, (int) $limit)->all(),
580
            'show_totals' => false,
581
        ]);
582
    }
583
584
    public function commonGivenListTotals(string $threshold = '1', string $limit = '10'): string
585
    {
586
        return view('lists/given-names-list', [
587
            'given_names' => $this->data->commonGivenNames('ALL', (int) $threshold, (int) $limit)->all(),
588
            'show_totals' => true,
589
        ]);
590
    }
591
592
    public function commonGivenMale(string $threshold = '1', string $limit = '10'): string
593
    {
594
        return $this->data->commonGivenNames('M', (int) $threshold, (int) $limit)
595
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
596
                $key => '<bdi>' . e($key) . '</bdi>',
597
            ])
598
            ->implode(I18N::$list_separator);
599
    }
600
601
    public function commonGivenMaleList(string $threshold = '1', string $limit = '10'): string
602
    {
603
        return view('lists/given-names-list', [
604
            'given_names' => $this->data->commonGivenNames('M', (int) $threshold, (int) $limit)->all(),
605
            'show_totals' => false,
606
        ]);
607
    }
608
609
    public function commonGivenMaleListTotals(string $threshold = '1', string $limit = '10'): string
610
    {
611
        return view('lists/given-names-list', [
612
            'given_names' => $this->data->commonGivenNames('M', (int) $threshold, (int) $limit)->all(),
613
            'show_totals' => true,
614
        ]);
615
    }
616
617
    public function commonGivenMaleTable(string $threshold = '1', string $limit = '10'): string
618
    {
619
        return view('lists/given-names-table', [
620
            'given_names' => $this->data->commonGivenNames('M', (int) $threshold, (int) $limit)->all(),
621
            'order'       => [[1, 'desc']],
622
        ]);
623
    }
624
625
    public function commonGivenMaleTotals(string $threshold = '1', string $limit = '10'): string
626
    {
627
        return $this->data->commonGivenNames('M', (int) $threshold, (int) $limit)
628
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
629
                $key => '<bdi>' . e($key) . '</bdi> (' . I18N::number($value) . ')',
630
            ])
631
            ->implode(I18N::$list_separator);
632
    }
633
634
    public function commonGivenOther(string $threshold = '1', string $limit = '10'): string
635
    {
636
        return $this->data->commonGivenNames('X', (int) $threshold, (int) $limit)
637
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
638
                $key => '<bdi>' . e($key) . '</bdi>',
639
            ])
640
            ->implode(I18N::$list_separator);
641
    }
642
643
    public function commonGivenOtherList(string $threshold = '1', string $limit = '10'): string
644
    {
645
        return view('lists/given-names-list', [
646
            'given_names' => $this->data->commonGivenNames('X', (int) $threshold, (int) $limit)->all(),
647
            'show_totals' => false,
648
        ]);
649
    }
650
651
    public function commonGivenOtherListTotals(string $threshold = '1', string $limit = '10'): string
652
    {
653
        return view('lists/given-names-list', [
654
            'given_names' => $this->data->commonGivenNames('X', (int) $threshold, (int) $limit)->all(),
655
            'show_totals' => true,
656
        ]);
657
    }
658
659
    public function commonGivenOtherTable(string $threshold = '1', string $limit = '10'): string
660
    {
661
        return view('lists/given-names-table', [
662
            'given_names' => $this->data->commonGivenNames('X', (int) $threshold, (int) $limit)->all(),
663
            'order'       => [[1, 'desc']],
664
        ]);
665
    }
666
667
    public function commonGivenOtherTotals(string $threshold = '1', string $limit = '10'): string
668
    {
669
        return $this->data->commonGivenNames('X', (int) $threshold, (int) $limit)
670
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
671
                $key => '<bdi>' . e($key) . '</bdi> (' . I18N::number($value) . ')',
672
            ])
673
            ->implode(I18N::$list_separator);
674
    }
675
676
    public function commonGivenTable(string $threshold = '1', string $limit = '10'): string
677
    {
678
        return view('lists/given-names-table', [
679
            'given_names' => $this->data->commonGivenNames('ALL', (int) $threshold, (int) $limit)->all(),
680
            'order'       => [[1, 'desc']],
681
        ]);
682
    }
683
684
    public function commonGivenTotals(string $threshold = '1', string $limit = '10'): string
685
    {
686
        return $this->data->commonGivenNames('ALL', (int) $threshold, (int) $limit)
687
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
688
                $key => '<bdi>' . e($key) . '</bdi> (' . I18N::number($value) . ')',
689
            ])
690
            ->implode(I18N::$list_separator);
691
    }
692
693
    public function commonGivenUnknown(string $threshold = '1', string $limit = '10'): string
694
    {
695
        return $this->data->commonGivenNames('U', (int) $threshold, (int) $limit)
696
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
697
                $key => '<bdi>' . e($key) . '</bdi>',
698
            ])
699
            ->implode(I18N::$list_separator);
700
    }
701
702
    public function commonGivenUnknownList(string $threshold = '1', string $limit = '10'): string
703
    {
704
        return view('lists/given-names-list', [
705
            'given_names' => $this->data->commonGivenNames('U', (int) $threshold, (int) $limit)->all(),
706
            'show_totals' => false,
707
        ]);
708
    }
709
710
    public function commonGivenUnknownListTotals(string $threshold = '1', string $limit = '10'): string
711
    {
712
        return view('lists/given-names-list', [
713
            'given_names' => $this->data->commonGivenNames('U', (int) $threshold, (int) $limit)->all(),
714
            'show_totals' => true,
715
        ]);
716
    }
717
718
    public function commonGivenUnknownTable(string $threshold = '1', string $limit = '10'): string
719
    {
720
        return view('lists/given-names-table', [
721
            'given_names' => $this->data->commonGivenNames('U', (int) $threshold, (int) $limit)->all(),
722
            'order'       => [[1, 'desc']],
723
        ]);
724
    }
725
726
    public function commonGivenUnknownTotals(string $threshold = '1', string $limit = '10'): string
727
    {
728
        return $this->data->commonGivenNames('U', (int) $threshold, (int) $limit)
729
            ->mapWithKeys(static fn (int $value, mixed $key): array => [
730
                $key => '<bdi>' . e($key) . '</bdi> (' . I18N::number($value) . ')',
731
            ])
732
            ->implode(I18N::$list_separator);
733
    }
734
735
    public function commonMarriagePlacesList(string $limit = '10'): string
736
    {
737
        return view('statistics/other/top10-list', ['records' => $this->data->countPlacesForFamilies('MARR', (int) $limit)]);
738
    }
739
740
    public function commonSurnames(string $threshold = '1', string $limit = '10', string $sort = 'alpha'): string
741
    {
742
        return $this->data->commonSurnamesQuery('nolist', false, (int) $threshold, (int) $limit, $sort);
743
    }
744
745
    public function commonSurnamesList(string $threshold = '1', string $limit = '10', string $sort = 'alpha'): string
746
    {
747
        return $this->data->commonSurnamesQuery('list', false, (int) $threshold, (int) $limit, $sort);
748
    }
749
750
    public function commonSurnamesListTotals(string $threshold = '1', string $limit = '10', string $sort = 'count'): string
751
    {
752
        return $this->data->commonSurnamesQuery('list', true, (int) $threshold, (int) $limit, $sort);
753
    }
754
755
    public function commonSurnamesTotals(string $threshold = '1', string $limit = '10', string $sort = 'count'): string
756
    {
757
        return $this->data->commonSurnamesQuery('nolist', true, (int) $threshold, (int) $limit, $sort);
758
    }
759
760
    public function contactGedcom(): string
761
    {
762
        $user_id = (int) $this->tree->getPreference('CONTACT_USER_ID');
763
        $user    = $this->user_service->find($user_id);
764
765
        if ($user instanceof User) {
766
            $request = app(ServerRequestInterface::class);
767
768
            return $this->user_service->contactLink($user, $request);
769
        }
770
771
        return '';
772
    }
773
774
    public function contactWebmaster(): string
775
    {
776
        $user_id = (int) $this->tree->getPreference('WEBMASTER_USER_ID');
777
        $user    = $this->user_service->find($user_id);
778
779
        if ($user instanceof User) {
780
            $request = app(ServerRequestInterface::class);
781
782
            return $this->user_service->contactLink($user, $request);
783
        }
784
785
        return '';
786
    }
787
788
    public function embedTags(string $text): string
789
    {
790
        return strtr($text, $this->getTags($text));
791
    }
792
793
    public function firstBirth(): string
794
    {
795
        return $this->data->firstEventRecord(['BIRT'], true);
796
    }
797
798
    public function firstBirthName(): string
799
    {
800
        return $this->data->firstEventName(['BIRT'], true);
801
    }
802
803
    public function firstBirthPlace(): string
804
    {
805
        return $this->data->firstEventPlace(['BIRT'], true);
806
    }
807
808
    public function firstBirthYear(): string
809
    {
810
        return $this->data->firstEventYear(['BIRT'], true);
811
    }
812
813
    public function firstDeath(): string
814
    {
815
        return $this->data->firstEventRecord(['DEAT'], true);
816
    }
817
818
    public function firstDeathName(): string
819
    {
820
        return $this->data->firstEventName(['DEAT'], true);
821
    }
822
823
    public function firstDeathPlace(): string
824
    {
825
        return $this->data->firstEventPlace(['DEAT'], true);
826
    }
827
828
    public function firstDeathYear(): string
829
    {
830
        return $this->data->firstEventYear(['DEAT'], true);
831
    }
832
833
    public function firstDivorce(): string
834
    {
835
        return $this->data->firstEventRecord(['DIV'], true);
836
    }
837
838
    public function firstDivorceName(): string
839
    {
840
        return $this->data->firstEventName(['DIV'], true);
841
    }
842
843
    public function firstDivorcePlace(): string
844
    {
845
        return $this->data->firstEventPlace(['DIV'], true);
846
    }
847
848
    public function firstDivorceYear(): string
849
    {
850
        return $this->data->firstEventYear(['DIV'], true);
851
    }
852
853
    public function firstEvent(): string
854
    {
855
        return $this->data->firstEventRecord([], true);
856
    }
857
858
    public function firstEventName(): string
859
    {
860
        return $this->data->firstEventName([], true);
861
    }
862
863
    public function firstEventPlace(): string
864
    {
865
        return $this->data->firstEventPlace([], true);
866
    }
867
868
    public function firstEventType(): string
869
    {
870
        return $this->data->firstEventType([], true);
871
    }
872
873
    public function firstEventYear(): string
874
    {
875
        return $this->data->firstEventYear([], true);
876
    }
877
878
    public function firstMarriage(): string
879
    {
880
        return $this->data->firstEventRecord(['MARR'], true);
881
    }
882
883
    public function firstMarriageName(): string
884
    {
885
        return $this->data->firstEventName(['MARR'], true);
886
    }
887
888
    public function firstMarriagePlace(): string
889
    {
890
        return $this->data->firstEventPlace(['MARR'], true);
891
    }
892
893
    public function firstMarriageYear(): string
894
    {
895
        return $this->data->firstEventYear(['MARR'], true);
896
    }
897
898
    public function gedcomCreatedSoftware(): string
899
    {
900
        $head = Registry::headerFactory()->make('HEAD', $this->tree);
901
902
        if ($head instanceof Header) {
903
            $sour = $head->facts(['SOUR'])->first();
904
905
            if ($sour instanceof Fact) {
906
                return $sour->attribute('NAME');
907
            }
908
        }
909
910
        return '';
911
    }
912
913
    public function gedcomCreatedVersion(): string
914
    {
915
        $head = Registry::headerFactory()->make('HEAD', $this->tree);
916
917
        if ($head instanceof Header) {
918
            $sour = $head->facts(['SOUR'])->first();
919
920
            if ($sour instanceof Fact) {
921
                $version = $sour->attribute('VERS');
922
923
                if (str_contains($version, 'Family Tree Maker ')) {
924
                    $p       = strpos($version, '(') + 1;
925
                    $p2      = strpos($version, ')');
926
                    $version = substr($version, $p, $p2 - $p);
927
                }
928
929
                // Fix EasyTree version
930
                if ($sour->value() === 'EasyTree') {
931
                    $version = substr($version, 1);
932
                }
933
934
                return $version;
935
            }
936
        }
937
938
        return '';
939
    }
940
941
    public function gedcomDate(): string
942
    {
943
        $head = Registry::headerFactory()->make('HEAD', $this->tree);
944
945
        if ($head instanceof Header) {
946
            $fact = $head->facts(['DATE'])->first();
947
948
            if ($fact instanceof Fact) {
949
                try {
950
                    return Registry::timestampFactory()->fromString($fact->value(), 'j M Y')->isoFormat('LL');
951
                } catch (InvalidArgumentException $ex) {
952
                    // HEAD:DATE invalid.
953
                }
954
            }
955
        }
956
957
        return '';
958
    }
959
960
    public function gedcomFavorites(): string
961
    {
962
        return $this->callBlock('gedcom_favorites');
963
    }
964
965
    public function gedcomFilename(): string
966
    {
967
        return $this->tree->name();
968
    }
969
970
    public function gedcomRootId(): string
971
    {
972
        return $this->tree->getPreference('PEDIGREE_ROOT_ID');
973
    }
974
975
    public function gedcomTitle(): string
976
    {
977
        return e($this->tree->title());
978
    }
979
980
    public function gedcomUpdated(): string
981
    {
982
        $row = DB::table('change')
983
            ->where('gedcom_id', '=', $this->tree->id())
984
            ->where('status', '=', 'accepted')
985
            ->orderBy('change_id', 'DESC')
986
            ->select(['change_time'])
987
            ->first();
988
989
        if ($row === null) {
990
            return $this->gedcomDate();
991
        }
992
993
        return Registry::timestampFactory()->fromString($row->change_time)->isoFormat('LL');
994
    }
995
996
    public function getAllTagsTable(): string
997
    {
998
        try {
999
            $class = new ReflectionClass($this);
1000
1001
            $public_methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
1002
1003
            $exclude = ['embedTags', 'getAllTagsTable'];
1004
1005
            $examples = Collection::make($public_methods)
1006
                ->filter(static fn (ReflectionMethod $method): bool => !in_array($method->getName(), $exclude, true))
1007
                ->filter(static fn(ReflectionMethod $method): bool => $method->getReturnType() instanceof ReflectionNamedType && $method->getReturnType()->getName() === 'string')
1008
                ->sort(static fn (ReflectionMethod $x, ReflectionMethod $y): int => $x->getName() <=> $y->getName())
1009
                ->map(function (ReflectionMethod $method): string {
1010
                    $tag = $method->getName();
1011
1012
                    return '<dt>#' . $tag . '#</dt><dd>' . $this->$tag() . '</dd>';
1013
                });
1014
1015
            return '<dl>' . $examples->implode('') . '</dl>';
1016
        } catch (ReflectionException $ex) {
1017
            return $ex->getMessage();
1018
        }
1019
    }
1020
1021
    public function getCommonSurname(): string
1022
    {
1023
        $top_surname = $this->data->commonSurnames(1, 0, 'count');
1024
1025
        return implode(I18N::$list_separator, array_keys(array_shift($top_surname) ?? []));
1026
    }
1027
1028
    /**
1029
     * @return array<string,string>
1030
     */
1031
    private function getTags(string $text): array
1032
    {
1033
        $tags    = [];
1034
        $matches = [];
1035
1036
        preg_match_all('/#([^#\n]+)(?=#)/', $text, $matches, PREG_SET_ORDER);
1037
1038
        foreach ($matches as $match) {
1039
            $params = explode(':', $match[1]);
1040
            $method = array_shift($params);
1041
1042
            if (method_exists($this, $method)) {
1043
                $tags[$match[0] . '#'] = $this->$method(...$params);
1044
            }
1045
        }
1046
1047
        return $tags;
1048
    }
1049
1050
    public function hitCount(): string
1051
    {
1052
        return $this->format->hitCount($this->data->countHits('index.php', 'gedcom:' . $this->tree->id()));
1053
    }
1054
1055
    public function hitCountFam(string $xref = ''): string
1056
    {
1057
        return $this->format->hitCount($this->data->countHits('family.php', $xref));
1058
    }
1059
1060
    public function hitCountIndi(string $xref = ''): string
1061
    {
1062
        return $this->format->hitCount($this->data->countHits('individual.php', $xref));
1063
    }
1064
1065
    public function hitCountNote(string $xref = ''): string
1066
    {
1067
        return $this->format->hitCount($this->data->countHits('note.php', $xref));
1068
    }
1069
1070
    public function hitCountObje(string $xref = ''): string
1071
    {
1072
        return $this->format->hitCount($this->data->countHits('mediaviewer.php', $xref));
1073
    }
1074
1075
    public function hitCountRepo(string $xref = ''): string
1076
    {
1077
        return $this->format->hitCount($this->data->countHits('repo.php', $xref));
1078
    }
1079
1080
    public function hitCountSour(string $xref = ''): string
1081
    {
1082
        return $this->format->hitCount($this->data->countHits('source.php', $xref));
1083
    }
1084
1085
    public function hitCountUser(): string
1086
    {
1087
        return $this->format->hitCount($this->data->countHits('index.php', 'user:' . Auth::id()));
1088
    }
1089
1090
    public function largestFamily(): string
1091
    {
1092
        $family = $this->data->familiesWithTheMostChildren(1)[0]->family ?? null;
1093
1094
        if ($family === null) {
1095
            return $this->format->missing();
1096
        }
1097
1098
        return $family->formatList();
1099
    }
1100
1101
    public function largestFamilyName(): string
1102
    {
1103
        return $this->format->record($this->data->familiesWithTheMostChildren(1)[0]->family ?? null);
1104
    }
1105
1106
    public function largestFamilySize(): string
1107
    {
1108
        return I18N::number($this->data->familiesWithTheMostChildren(1)[0]->children ?? 0);
1109
    }
1110
1111
    public function lastBirth(): string
1112
    {
1113
        return $this->data->firstEventRecord(['BIRT'], false);
1114
    }
1115
1116
    public function lastBirthName(): string
1117
    {
1118
        return $this->data->firstEventName(['BIRT'], false);
1119
    }
1120
1121
    public function lastBirthPlace(): string
1122
    {
1123
        return $this->data->firstEventPlace(['BIRT'], false);
1124
    }
1125
1126
    public function lastBirthYear(): string
1127
    {
1128
        return $this->data->firstEventYear(['BIRT'], false);
1129
    }
1130
1131
    public function lastDeath(): string
1132
    {
1133
        return $this->data->firstEventRecord(['DEAT'], false);
1134
    }
1135
1136
    public function lastDeathName(): string
1137
    {
1138
        return $this->data->firstEventName(['DEAT'], false);
1139
    }
1140
1141
    public function lastDeathPlace(): string
1142
    {
1143
        return $this->data->firstEventPlace(['DEAT'], false);
1144
    }
1145
1146
    public function lastDeathYear(): string
1147
    {
1148
        return $this->data->firstEventYear(['DEAT'], false);
1149
    }
1150
1151
    public function lastDivorce(): string
1152
    {
1153
        return $this->data->firstEventRecord(['DIV'], false);
1154
    }
1155
1156
    public function lastDivorceName(): string
1157
    {
1158
        return $this->data->firstEventName(['DIV'], true);
1159
    }
1160
1161
    public function lastDivorcePlace(): string
1162
    {
1163
        return $this->data->firstEventPlace(['DIV'], true);
1164
    }
1165
1166
    public function lastDivorceYear(): string
1167
    {
1168
        return $this->data->firstEventYear(['DIV'], true);
1169
    }
1170
1171
    public function lastEvent(): string
1172
    {
1173
        return $this->data->firstEventRecord([], false);
1174
    }
1175
1176
    public function lastEventName(): string
1177
    {
1178
        return $this->data->firstEventName([], false);
1179
    }
1180
1181
    public function lastEventPlace(): string
1182
    {
1183
        return $this->data->firstEventPlace([], false);
1184
    }
1185
1186
    public function lastEventType(): string
1187
    {
1188
        return $this->data->firstEventType([], false);
1189
    }
1190
1191
    public function lastEventYear(): string
1192
    {
1193
        return $this->data->firstEventYear([], false);
1194
    }
1195
1196
    public function lastMarriage(): string
1197
    {
1198
        return $this->data->firstEventRecord(['MARR'], false);
1199
    }
1200
1201
    public function lastMarriageName(): string
1202
    {
1203
        return $this->data->firstEventName(['MARR'], false);
1204
    }
1205
1206
    public function lastMarriagePlace(): string
1207
    {
1208
        return $this->data->firstEventPlace(['MARR'], false);
1209
    }
1210
1211
    public function lastMarriageYear(): string
1212
    {
1213
        return $this->data->firstEventYear(['MARR'], false);
1214
    }
1215
1216
    public function latestUserFullName(): string
1217
    {
1218
        $user = $this->user_service->find($this->data->latestUserId()) ?? Auth::user();
1219
1220
        return e($user->realName());
1221
    }
1222
1223
    public function latestUserId(): string
1224
    {
1225
        $user = $this->user_service->find($this->data->latestUserId()) ?? Auth::user();
1226
1227
        return (string) $user->id();
1228
    }
1229
1230
    public function latestUserLoggedin(?string $yes = null, ?string $no = null): string
1231
    {
1232
        if ($this->data->isUserLoggedIn($this->data->latestUserId())) {
1233
            return $yes ?? I18N::translate('Yes');
1234
        }
1235
1236
        return $no ?? I18N::translate('No');
1237
    }
1238
1239
    public function latestUserName(): string
1240
    {
1241
        $user = $this->user_service->find($this->data->latestUserId()) ?? Auth::user();
1242
1243
        return e($user->userName());
1244
    }
1245
1246
    public function latestUserRegDate(?string $format = null): string
1247
    {
1248
        $format    ??= I18N::dateFormat();
1249
        $user      = $this->user_service->find($this->data->latestUserId()) ?? Auth::user();
1250
        $timestamp = (int) $user->getPreference(UserInterface::PREF_TIMESTAMP_REGISTERED);
1251
1252
        if ($timestamp === 0) {
1253
            return I18N::translate('Never');
1254
        }
1255
1256
        return Registry::timestampFactory()->make($timestamp)->format(strtr($format, ['%' => '']));
1257
    }
1258
1259
    public function latestUserRegTime(?string $format = null): string
1260
    {
1261
        $format    ??= I18N::timeFormat();
1262
        $user      = $this->user_service->find($this->data->latestUserId()) ?? Auth::user();
1263
        $timestamp = (int) $user->getPreference(UserInterface::PREF_TIMESTAMP_REGISTERED);
1264
1265
        if ($timestamp === 0) {
1266
            return I18N::translate('Never');
1267
        }
1268
1269
        return Registry::timestampFactory()->make($timestamp)->format(strtr($format, ['%' => '']));
1270
    }
1271
1272
    public function longestLife(): string
1273
    {
1274
        $row = $this->data->longlifeQuery('ALL');
1275
1276
        if ($row === null) {
1277
            return '';
1278
        }
1279
1280
        return $row->individual->formatList();
1281
    }
1282
1283
    public function longestLifeAge(): string
1284
    {
1285
        $row = $this->data->longlifeQuery('ALL');
1286
1287
        if ($row === null) {
1288
            return '';
1289
        }
1290
1291
        return I18N::number((int) ($row->days / 365.25));
1292
    }
1293
1294
    public function longestLifeFemale(): string
1295
    {
1296
        $row = $this->data->longlifeQuery('F');
1297
1298
        if ($row === null) {
1299
            return '';
1300
        }
1301
1302
        return $row->individual->formatList();
1303
    }
1304
1305
    public function longestLifeFemaleAge(): string
1306
    {
1307
        $row = $this->data->longlifeQuery('F');
1308
1309
        if ($row === null) {
1310
            return '';
1311
        }
1312
1313
        return I18N::number((int) ($row->days / 365.25));
1314
    }
1315
1316
    public function longestLifeFemaleName(): string
1317
    {
1318
        return $this->format->record($this->data->longlifeQuery('F')->individual ?? null);
1319
    }
1320
1321
    public function longestLifeMale(): string
1322
    {
1323
        $row = $this->data->longlifeQuery('M');
1324
1325
        if ($row === null) {
1326
            return '';
1327
        }
1328
1329
        return $row->individual->formatList();
1330
    }
1331
1332
    public function longestLifeMaleAge(): string
1333
    {
1334
        $row = $this->data->longlifeQuery('M');
1335
1336
        if ($row === null) {
1337
            return '';
1338
        }
1339
1340
        return I18N::number((int) ($row->days / 365.25));
1341
    }
1342
1343
    public function longestLifeMaleName(): string
1344
    {
1345
        return $this->format->record($this->data->longlifeQuery('M')->individual ?? null);
1346
    }
1347
1348
    public function longestLifeName(): string
1349
    {
1350
        return $this->format->record($this->data->longlifeQuery('ALL')->individual ?? null);
1351
    }
1352
1353
    public function minAgeOfMarriage(): string
1354
    {
1355
        return $this->data->ageOfMarriageQuery('age', 'ASC', 1);
1356
    }
1357
1358
    public function minAgeOfMarriageFamilies(string $limit = '10'): string
1359
    {
1360
        return $this->data->ageOfMarriageQuery('nolist', 'ASC', (int) $limit);
1361
    }
1362
1363
    public function minAgeOfMarriageFamiliesList(string $limit = '10'): string
1364
    {
1365
        return $this->data->ageOfMarriageQuery('list', 'ASC', (int) $limit);
1366
    }
1367
1368
    public function minAgeOfMarriageFamily(): string
1369
    {
1370
        return $this->data->ageOfMarriageQuery('name', 'ASC', 1);
1371
    }
1372
1373
    public function noChildrenFamilies(): string
1374
    {
1375
        return I18N::number($this->data->countFamiliesWithNoChildren());
1376
    }
1377
1378
    public function noChildrenFamiliesList(string $type = 'list'): string
1379
    {
1380
        return $this->data->noChildrenFamiliesList($type);
1381
    }
1382
1383
    public function oldestFather(): string
1384
    {
1385
        return $this->data->parentsQuery('full', 'DESC', 'M', false);
1386
    }
1387
1388
    public function oldestFatherAge(string $show_years = '0'): string
1389
    {
1390
        return $this->data->parentsQuery('age', 'DESC', 'M', (bool) $show_years);
1391
    }
1392
1393
    public function oldestFatherName(): string
1394
    {
1395
        return $this->data->parentsQuery('name', 'DESC', 'M', false);
1396
    }
1397
1398
    public function oldestMarriageFemale(): string
1399
    {
1400
        return $this->data->marriageQuery('full', 'DESC', 'F', false);
1401
    }
1402
1403
    public function oldestMarriageFemaleAge(string $show_years = '0'): string
1404
    {
1405
        return $this->data->marriageQuery('age', 'DESC', 'F', (bool) $show_years);
1406
    }
1407
1408
    public function oldestMarriageFemaleName(): string
1409
    {
1410
        return $this->data->marriageQuery('name', 'DESC', 'F', false);
1411
    }
1412
1413
    public function oldestMarriageMale(): string
1414
    {
1415
        return $this->data->marriageQuery('full', 'DESC', 'M', false);
1416
    }
1417
1418
    public function oldestMarriageMaleAge(string $show_years = '0'): string
1419
    {
1420
        return $this->data->marriageQuery('age', 'DESC', 'M', (bool) $show_years);
1421
    }
1422
1423
    public function oldestMarriageMaleName(): string
1424
    {
1425
        return $this->data->marriageQuery('name', 'DESC', 'M', false);
1426
    }
1427
1428
    public function oldestMother(): string
1429
    {
1430
        return $this->data->parentsQuery('full', 'DESC', 'F', false);
1431
    }
1432
1433
    public function oldestMotherAge(string $show_years = '0'): string
1434
    {
1435
        return $this->data->parentsQuery('age', 'DESC', 'F', (bool) $show_years);
1436
    }
1437
1438
    public function oldestMotherName(): string
1439
    {
1440
        return $this->data->parentsQuery('name', 'DESC', 'F', false);
1441
    }
1442
1443
    public function serverDate(): string
1444
    {
1445
        return Registry::timestampFactory()->now(new SiteUser())->format(strtr(I18N::dateFormat(), ['%' => '']));
1446
    }
1447
1448
    public function serverTime(): string
1449
    {
1450
        return Registry::timestampFactory()->now(new SiteUser())->format(strtr(I18N::timeFormat(), ['%' => '']));
1451
    }
1452
1453
    public function serverTime24(): string
1454
    {
1455
        return Registry::timestampFactory()->now(new SiteUser())->format('G:i');
1456
    }
1457
1458
    public function serverTimezone(): string
1459
    {
1460
        return Registry::timestampFactory()->now(new SiteUser())->format('T');
1461
    }
1462
1463
    public function statsAge(): string
1464
    {
1465
        $records = $this->data->statsAge();
1466
1467
        $out = [];
1468
1469
        foreach ($records as $record) {
1470
            $out[$record->century][$record->sex] = $record->age;
1471
        }
1472
1473
        $data = [
1474
            [
1475
                I18N::translate('Century'),
1476
                I18N::translate('Males'),
1477
                I18N::translate('Females'),
1478
                I18N::translate('Average age'),
1479
            ]
1480
        ];
1481
1482
        foreach ($out as $century => $values) {
1483
            $female_age  = $values['F'] ?? 0;
1484
            $male_age    = $values['M'] ?? 0;
1485
            $average_age = ($female_age + $male_age) / 2.0;
1486
1487
            $data[] = [
1488
                $this->format->century($century),
1489
                round($male_age, 1),
1490
                round($female_age, 1),
1491
                round($average_age, 1),
1492
            ];
1493
        }
1494
1495
        $chart_title   = I18N::translate('Average age related to death century');
1496
        $chart_options = [
1497
            'title' => $chart_title,
1498
            'subtitle' => I18N::translate('Average age at death'),
1499
            'vAxis' => [
1500
                'title' => I18N::translate('Age'),
1501
            ],
1502
            'hAxis' => [
1503
                'showTextEvery' => 1,
1504
                'slantedText'   => false,
1505
                'title'         => I18N::translate('Century'),
1506
            ],
1507
            'colors' => [
1508
                '#84beff',
1509
                '#ffd1dc',
1510
                '#ff0000',
1511
            ],
1512
        ];
1513
1514
        return view('statistics/other/charts/combo', [
1515
            'data'          => $data,
1516
            'chart_options' => $chart_options,
1517
            'chart_title'   => $chart_title,
1518
            'language'      => I18N::languageTag(),
1519
        ]);
1520
    }
1521
1522
    public function statsBirth(string $color1 = 'ffffff', string $color2 = '84beff'): string
1523
    {
1524
        $data   = $this->data->countEventsByCentury('BIRT');
1525
        $colors = $this->format->interpolateRgb($color1, $color2, count($data));
1526
1527
        return $this->format->pieChart(
1528
            $data,
1529
            $colors,
1530
            I18N::translate('Births by century'),
1531
            I18N::translate('Century'),
1532
            I18N::translate('Total'),
1533
        );
1534
    }
1535
1536
    public function statsChildren(): string
1537
    {
1538
        $records = DB::table('families')
1539
            ->selectRaw('AVG(f_numchil) AS total')
1540
            ->selectRaw('ROUND((d_year + 49) / 100, 0) AS century')
1541
            ->join('dates', static function (JoinClause $join): void {
1542
                $join->on('d_file', '=', 'f_file')
1543
                    ->on('d_gid', '=', 'f_id');
1544
            })
1545
            ->where('f_file', '=', $this->tree->id())
1546
            ->where('d_julianday1', '<>', 0)
1547
            ->where('d_fact', '=', 'MARR')
1548
            ->whereIn('d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1549
            ->groupBy(['century'])
1550
            ->orderBy('century')
1551
            ->get()
1552
            ->map(static fn (object $row): object => (object) [
1553
                'century' => (int) $row->century,
1554
                'total'   => (float) $row->total,
1555
            ]);
1556
1557
        $data = [
1558
            [
1559
                I18N::translate('Century'),
1560
                I18N::translate('Average number'),
1561
            ],
1562
        ];
1563
1564
        foreach ($records as $record) {
1565
            $data[] = [
1566
                $this->format->century($record->century),
1567
                round($record->total, 2),
1568
            ];
1569
        }
1570
1571
        $chart_title   = I18N::translate('Average number of children per family');
1572
        $chart_options = [
1573
            'title'    => $chart_title,
1574
            'subtitle' => '',
1575
            'legend'   => [
1576
                'position' => 'none',
1577
            ],
1578
            'vAxis'    => [
1579
                'title' => I18N::translate('Number of children'),
1580
            ],
1581
            'hAxis'    => [
1582
                'showTextEvery' => 1,
1583
                'slantedText'   => false,
1584
                'title'         => I18N::translate('Century'),
1585
            ],
1586
            'colors'   => [
1587
                '#84beff',
1588
            ],
1589
        ];
1590
1591
        return view('statistics/other/charts/column', [
1592
            'data'          => $data,
1593
            'chart_options' => $chart_options,
1594
            'chart_title'   => $chart_title,
1595
            'language'      => I18N::languageTag(),
1596
        ]);
1597
    }
1598
1599
    /**
1600
     * @return array<object{f_numchil:int,total:int}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<object{f_numchil:int,total:int}> at position 2 could not be parsed: Expected '>' at position 2, but found 'object'.
Loading history...
1601
     */
1602
    public function statsChildrenQuery(int $year1 = 0, int $year2 = 0): array
1603
    {
1604
        return $this->data->statsChildrenQuery($year1, $year2);
1605
    }
1606
1607
    public function statsDeath(string $color1 = 'ffffff', string $color2 = '84beff'): string
1608
    {
1609
        $data   = $this->data->countEventsByCentury('DEAT');
1610
        $colors = $this->format->interpolateRgb($color1, $color2, count($data));
1611
1612
        return $this->format->pieChart(
1613
            $data,
1614
            $colors,
1615
            I18N::translate('Births by century'),
1616
            I18N::translate('Century'),
1617
            I18N::translate('Total'),
1618
        );
1619
    }
1620
1621
    public function statsDiv(string $color1 = 'ffffff', string $color2 = '84beff'): string
1622
    {
1623
        $data   = $this->data->countEventsByCentury('DIV');
1624
        $colors = $this->format->interpolateRgb($color1, $color2, count($data));
1625
1626
        return $this->format->pieChart(
1627
            $data,
1628
            $colors,
1629
            I18N::translate('Divorces by century'),
1630
            I18N::translate('Century'),
1631
            I18N::translate('Total'),
1632
        );
1633
    }
1634
1635
    public function statsMarr(string $color1 = 'ffffff', string $color2 = '84beff'): string
1636
    {
1637
        $data   = $this->data->countEventsByCentury('MARR');
1638
        $colors = $this->format->interpolateRgb($color1, $color2, count($data));
1639
1640
        return $this->format->pieChart(
1641
            $data,
1642
            $colors,
1643
            I18N::translate('Marriages by century'),
1644
            I18N::translate('Century'),
1645
            I18N::translate('Total'),
1646
        );
1647
    }
1648
1649
    public function statsMarrAge(): string
1650
    {
1651
        $prefix = DB::connection()->getTablePrefix();
1652
1653
        $out = [];
1654
1655
        $male = DB::table('dates as married')
1656
            ->select([
1657
                new Expression('AVG(' . $prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 - 182.5) / 365.25 AS age'),
1658
                new Expression('ROUND((' . $prefix . 'married.d_year + 49) / 100, 0) AS century'),
1659
                new Expression("'M' as sex"),
1660
            ])
1661
            ->join('families as fam', static function (JoinClause $join): void {
1662
                $join->on('fam.f_id', '=', 'married.d_gid')
1663
                    ->on('fam.f_file', '=', 'married.d_file');
1664
            })
1665
            ->join('dates as birth', static function (JoinClause $join): void {
1666
                $join->on('birth.d_gid', '=', 'fam.f_husb')
1667
                    ->on('birth.d_file', '=', 'fam.f_file');
1668
            })
1669
            ->whereIn('married.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1670
            ->where('married.d_file', '=', $this->tree->id())
1671
            ->where('married.d_fact', '=', 'MARR')
1672
            ->where('married.d_julianday1', '>', new Expression($prefix . 'birth.d_julianday1'))
1673
            ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1674
            ->where('birth.d_fact', '=', 'BIRT')
1675
            ->where('birth.d_julianday1', '<>', 0)
1676
            ->groupBy(['century', 'sex']);
1677
1678
        $female = DB::table('dates as married')
1679
            ->select([
1680
                new Expression('ROUND(AVG(' . $prefix . 'married.d_julianday2 - ' . $prefix . 'birth.d_julianday1 - 182.5) / 365.25, 1) AS age'),
1681
                new Expression('ROUND((' . $prefix . 'married.d_year + 49) / 100, 0) AS century'),
1682
                new Expression("'F' as sex"),
1683
            ])
1684
            ->join('families as fam', static function (JoinClause $join): void {
1685
                $join->on('fam.f_id', '=', 'married.d_gid')
1686
                    ->on('fam.f_file', '=', 'married.d_file');
1687
            })
1688
            ->join('dates as birth', static function (JoinClause $join): void {
1689
                $join->on('birth.d_gid', '=', 'fam.f_wife')
1690
                    ->on('birth.d_file', '=', 'fam.f_file');
1691
            })
1692
            ->whereIn('married.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1693
            ->where('married.d_file', '=', $this->tree->id())
1694
            ->where('married.d_fact', '=', 'MARR')
1695
            ->where('married.d_julianday1', '>', new Expression($prefix . 'birth.d_julianday1'))
1696
            ->whereIn('birth.d_type', ['@#DGREGORIAN@', '@#DJULIAN@'])
1697
            ->where('birth.d_fact', '=', 'BIRT')
1698
            ->where('birth.d_julianday1', '<>', 0)
1699
            ->groupBy(['century', 'sex']);
1700
1701
        $records = $male->unionAll($female)
1702
            ->orderBy('century')
1703
            ->get()
1704
            ->map(static fn (object $row): object => (object) [
1705
                'age'     => (float) $row->age,
1706
                'century' => (int) $row->century,
1707
                'sex'     => $row->sex,
1708
            ]);
1709
1710
1711
        foreach ($records as $record) {
1712
            $out[$record->century][$record->sex] = $record->age;
1713
        }
1714
1715
        $data = [
1716
            [
1717
                I18N::translate('Century'),
1718
                I18N::translate('Males'),
1719
                I18N::translate('Females'),
1720
                I18N::translate('Average age'),
1721
            ],
1722
        ];
1723
1724
        foreach ($out as $century => $values) {
1725
            $female_age  = $values['F'] ?? 0;
1726
            $male_age    = $values['M'] ?? 0;
1727
            $average_age = ($female_age + $male_age) / 2.0;
1728
1729
            $data[] = [
1730
                $this->format->century($century),
1731
                round($male_age, 1),
1732
                round($female_age, 1),
1733
                round($average_age, 1),
1734
            ];
1735
        }
1736
1737
        $chart_title   = I18N::translate('Average age in century of marriage');
1738
        $chart_options = [
1739
            'title'    => $chart_title,
1740
            'subtitle' => I18N::translate('Average age at marriage'),
1741
            'vAxis'    => [
1742
                'title' => I18N::translate('Age'),
1743
            ],
1744
            'hAxis'    => [
1745
                'showTextEvery' => 1,
1746
                'slantedText'   => false,
1747
                'title'         => I18N::translate('Century'),
1748
            ],
1749
            'colors'   => [
1750
                '#84beff',
1751
                '#ffd1dc',
1752
                '#ff0000',
1753
            ],
1754
        ];
1755
1756
        return view('statistics/other/charts/combo', [
1757
            'data'          => $data,
1758
            'chart_options' => $chart_options,
1759
            'chart_title'   => $chart_title,
1760
            'language'      => I18N::languageTag(),
1761
        ]);
1762
    }
1763
1764
    /**
1765
     * @return array<object{f_id:string,d_gid:string,age:int}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<object{f_id:string,d_gid:string,age:int}> at position 2 could not be parsed: Expected '>' at position 2, but found 'object'.
Loading history...
1766
     */
1767
    public function statsMarrAgeQuery(string $sex, int $year1 = 0, int $year2 = 0): array
1768
    {
1769
        return $this->data->statsMarrAgeQuery($sex, $year1, $year2);
1770
    }
1771
1772
    public function topAgeBetweenSiblings(): string
1773
    {
1774
        return $this->data->topAgeBetweenSiblings();
1775
    }
1776
1777
    public function topAgeBetweenSiblingsFullName(): string
1778
    {
1779
        return $this->data->topAgeBetweenSiblingsFullName();
1780
    }
1781
1782
    public function topAgeBetweenSiblingsList(string $limit = '10', string $one = '0'): string
1783
    {
1784
        return $this->data->topAgeBetweenSiblingsList((int) $limit, (bool) $one);
1785
    }
1786
1787
    public function topAgeBetweenSiblingsName(): string
1788
    {
1789
        $row = $this->data->maximumAgeBetweenSiblings(1)[0] ?? null;
1790
1791
        if ($row === null) {
1792
            return $this->format->missing();
1793
        }
1794
1795
        return
1796
            $this->format->record($row->child2) . ' ' .
1797
            I18N::translate('and') . ' ' .
1798
            $this->format->record($row->child1) . ' ' .
1799
            '<a href="' . e($row->family->url()) . '">[' . I18N::translate('View this family') . ']</a>';
1800
    }
1801
1802
    public function topAgeOfMarriage(): string
1803
    {
1804
        return $this->data->ageOfMarriageQuery('age', 'DESC', 1);
1805
    }
1806
1807
    public function topAgeOfMarriageFamilies(string $limit = '10'): string
1808
    {
1809
        return $this->data->ageOfMarriageQuery('nolist', 'DESC', (int) $limit);
1810
    }
1811
1812
    public function topAgeOfMarriageFamiliesList(string $limit = '10'): string
1813
    {
1814
        return $this->data->ageOfMarriageQuery('list', 'DESC', (int) $limit);
1815
    }
1816
1817
    public function topAgeOfMarriageFamily(): string
1818
    {
1819
        return $this->data->ageOfMarriageQuery('name', 'DESC', 1);
1820
    }
1821
1822
    public function topTenLargestFamily(string $limit = '10'): string
1823
    {
1824
        return $this->data->topTenLargestFamily((int) $limit);
1825
    }
1826
1827
    public function topTenLargestFamilyList(string $limit = '10'): string
1828
    {
1829
        return $this->data->topTenLargestFamilyList((int) $limit);
1830
    }
1831
1832
    public function topTenLargestGrandFamily(string $limit = '10'): string
1833
    {
1834
        return $this->data->topTenLargestGrandFamily((int) $limit);
1835
    }
1836
1837
    public function topTenLargestGrandFamilyList(string $limit = '10'): string
1838
    {
1839
        return $this->data->topTenLargestGrandFamilyList((int) $limit);
1840
    }
1841
1842
    public function topTenOldest(string $limit = '10'): string
1843
    {
1844
        $records = $this->data->topTenOldestQuery('ALL', (int) $limit)
1845
            ->map(fn (object $row): array => [
1846
                'person' => $row->individual,
1847
                'age'    => $this->format->age($row->days),
1848
            ])
1849
            ->all();
1850
1851
        return view('statistics/individuals/top10-nolist', [
1852
            'records' => $records,
1853
        ]);
1854
    }
1855
1856
    public function topTenOldestAlive(string $limit = '10'): string
1857
    {
1858
        if (!Auth::isMember($this->tree)) {
1859
            return I18N::translate('This information is private and cannot be shown.');
1860
        }
1861
1862
        $records = $this->data->topTenOldestAliveQuery('ALL', (int) $limit)
1863
            ->map(fn (Individual $individual): array => [
1864
                'person' => $individual,
1865
                'age'    => $this->format->age(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
1866
            ])
1867
            ->all();
1868
1869
        return view('statistics/individuals/top10-nolist', [
1870
            'records' => $records,
1871
        ]);
1872
    }
1873
1874
    public function topTenOldestFemale(string $limit = '10'): string
1875
    {
1876
        $records = $this->data->topTenOldestQuery('F', (int) $limit)
1877
            ->map(fn (object $row): array => [
1878
                'person' => $row->individual,
1879
                'age'    => $this->format->age($row->days),
1880
            ])
1881
            ->all();
1882
1883
        return view('statistics/individuals/top10-nolist', [
1884
            'records' => $records,
1885
        ]);
1886
    }
1887
1888
    public function topTenOldestFemaleAlive(string $limit = '10'): string
1889
    {
1890
        if (!Auth::isMember($this->tree)) {
1891
            return I18N::translate('This information is private and cannot be shown.');
1892
        }
1893
1894
        $records = $this->data->topTenOldestAliveQuery('F', (int) $limit)
1895
            ->map(fn (Individual $individual): array => [
1896
                'person' => $individual,
1897
                'age'    => $this->format->age(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
1898
            ])
1899
            ->all();
1900
1901
        return view('statistics/individuals/top10-nolist', [
1902
            'records' => $records,
1903
        ]);
1904
    }
1905
1906
    public function topTenOldestFemaleList(string $limit = '10'): string
1907
    {
1908
        $records = $this->data->topTenOldestQuery('F', (int) $limit)
1909
            ->map(fn (object $row): array => [
1910
                'person' => $row->individual,
1911
                'age'    => $this->format->age($row->days),
1912
            ])
1913
            ->all();
1914
1915
        return view('statistics/individuals/top10-list', [
1916
            'records' => $records,
1917
        ]);
1918
    }
1919
1920
    public function topTenOldestFemaleListAlive(string $limit = '10'): string
1921
    {
1922
        if (!Auth::isMember($this->tree)) {
1923
            return I18N::translate('This information is private and cannot be shown.');
1924
        }
1925
1926
        $records = $this->data->topTenOldestAliveQuery('F', (int) $limit)
1927
            ->map(fn (Individual $individual): array => [
1928
                'person' => $individual,
1929
                'age'    => $this->format->age(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
1930
            ])
1931
            ->all();
1932
1933
        return view('statistics/individuals/top10-list', [
1934
            'records' => $records,
1935
        ]);
1936
    }
1937
1938
    public function topTenOldestList(string $limit = '10'): string
1939
    {
1940
        $records = $this->data->topTenOldestQuery('ALL', (int) $limit)
1941
            ->map(fn (object $row): array => [
1942
                'person' => $row->individual,
1943
                'age'    => $this->format->age($row->days),
1944
            ])
1945
            ->all();
1946
1947
        return view('statistics/individuals/top10-list', [
1948
            'records' => $records,
1949
        ]);
1950
    }
1951
1952
    public function topTenOldestListAlive(string $limit = '10'): string
1953
    {
1954
        if (!Auth::isMember($this->tree)) {
1955
            return I18N::translate('This information is private and cannot be shown.');
1956
        }
1957
1958
        $records = $this->data->topTenOldestAliveQuery('ALL', (int) $limit)
1959
            ->map(fn (Individual $individual): array => [
1960
                'person' => $individual,
1961
                'age'    => $this->format->age(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
1962
            ])
1963
            ->all();
1964
1965
        return view('statistics/individuals/top10-list', [
1966
            'records' => $records,
1967
        ]);
1968
    }
1969
1970
    public function topTenOldestMale(string $limit = '10'): string
1971
    {
1972
        $records = $this->data->topTenOldestQuery('M', (int) $limit)
1973
            ->map(fn (object $row): array => [
1974
                'person' => $row->individual,
1975
                'age'    => $this->format->age($row->days),
1976
            ])
1977
            ->all();
1978
1979
        return view('statistics/individuals/top10-nolist', [
1980
            'records' => $records,
1981
        ]);
1982
    }
1983
1984
    public function topTenOldestMaleAlive(string $limit = '10'): string
1985
    {
1986
        if (!Auth::isMember($this->tree)) {
1987
            return I18N::translate('This information is private and cannot be shown.');
1988
        }
1989
1990
        $records = $this->data->topTenOldestAliveQuery('M', (int) $limit)
1991
            ->map(fn (Individual $individual): array => [
1992
                'person' => $individual,
1993
                'age'    => $this->format->age(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
1994
            ])
1995
            ->all();
1996
1997
        return view('statistics/individuals/top10-nolist', [
1998
            'records' => $records,
1999
        ]);
2000
    }
2001
2002
    public function topTenOldestMaleList(string $limit = '10'): string
2003
    {
2004
        $records = $this->data->topTenOldestQuery('M', (int) $limit)
2005
            ->map(fn (object $row): array => [
2006
                'person' => $row->individual,
2007
                'age'    => $this->format->age($row->days),
2008
            ])
2009
            ->all();
2010
2011
        return view('statistics/individuals/top10-list', [
2012
            'records' => $records,
2013
        ]);
2014
    }
2015
2016
    public function topTenOldestMaleListAlive(string $limit = '10'): string
2017
    {
2018
        if (!Auth::isMember($this->tree)) {
2019
            return I18N::translate('This information is private and cannot be shown.');
2020
        }
2021
2022
        $records = $this->data->topTenOldestAliveQuery('M', (int) $limit)
2023
            ->map(fn (Individual $individual): array => [
2024
                'person' => $individual,
2025
                'age'    => $this->format->age(Registry::timestampFactory()->now()->julianDay() - $individual->getBirthDate()->minimumJulianDay()),
2026
            ])
2027
            ->all();
2028
2029
        return view('statistics/individuals/top10-list', [
2030
            'records' => $records,
2031
        ]);
2032
    }
2033
2034
    public function totalAdmins(): string
2035
    {
2036
        return I18N::number($this->user_service->administrators()->count());
2037
    }
2038
2039
    public function totalBirths(): string
2040
    {
2041
        return I18N::number($this->data->countIndividualsWithEvents(['BIRT']));
2042
    }
2043
2044
    public function totalChildren(): string
2045
    {
2046
        return I18N::number($this->data->countChildren());
2047
    }
2048
2049
    public function totalDeaths(): string
2050
    {
2051
        return I18N::number($this->data->countIndividualsWithEvents(['DEAT']));
2052
    }
2053
2054
    public function totalDeceased(): string
2055
    {
2056
        return I18N::number($this->data->countIndividualsDeceased());
2057
    }
2058
2059
    public function totalDeceasedPercentage(): string
2060
    {
2061
        return $this->format->percentage(
2062
            $this->data->countIndividualsDeceased(),
2063
            $this->data->countIndividuals()
2064
        );
2065
    }
2066
2067
    public function totalDivorces(): string
2068
    {
2069
        return I18N::number($this->data->countFamiliesWithEvents(['DIV']));
2070
    }
2071
2072
    public function totalEvents(): string
2073
    {
2074
        return I18N::number($this->data->countOtherEvents(['CHAN']));
2075
    }
2076
2077
    public function totalEventsBirth(): string
2078
    {
2079
        return I18N::number($this->data->countAllEvents(Gedcom::BIRTH_EVENTS));
2080
    }
2081
2082
    public function totalEventsDeath(): string
2083
    {
2084
        return I18N::number($this->data->countAllEvents(Gedcom::DEATH_EVENTS));
2085
    }
2086
2087
    public function totalEventsDivorce(): string
2088
    {
2089
        return I18N::number($this->data->countAllEvents(Gedcom::DIVORCE_EVENTS));
2090
    }
2091
2092
    public function totalEventsMarriage(): string
2093
    {
2094
        return I18N::number($this->data->countAllEvents(Gedcom::MARRIAGE_EVENTS));
2095
    }
2096
2097
    public function totalEventsOther(): string
2098
    {
2099
        return I18N::number($this->data->countOtherEvents(array_merge(
2100
            ['CHAN'],
2101
            Gedcom::BIRTH_EVENTS,
2102
            Gedcom::DEATH_EVENTS,
2103
            Gedcom::MARRIAGE_EVENTS,
2104
            Gedcom::DIVORCE_EVENTS,
2105
        )));
2106
    }
2107
2108
    public function totalFamilies(): string
2109
    {
2110
        return I18N::number($this->data->countFamilies());
2111
    }
2112
2113
    public function totalFamiliesPercentage(): string
2114
    {
2115
        return $this->format->percentage(
2116
            $this->data->countFamilies(),
2117
            $this->data->countAllRecords()
2118
        );
2119
    }
2120
2121
    public function totalFamsWithSources(): string
2122
    {
2123
        return I18N::number($this->data->countFamiliesWithSources());
2124
    }
2125
2126
    public function totalFamsWithSourcesPercentage(): string
2127
    {
2128
        return $this->format->percentage(
2129
            $this->data->countFamiliesWithSources(),
2130
            $this->data->countFamilies()
2131
        );
2132
    }
2133
2134
    public function totalGedcomFavorites(): string
2135
    {
2136
        return I18N::number($this->data->countTreeFavorites());
2137
    }
2138
2139
    public function totalGivennames(string ...$names): string
2140
    {
2141
        return I18N::number($this->data->countGivenNames($names));
2142
    }
2143
2144
    public function totalIndisWithSources(): string
2145
    {
2146
        return I18N::number($this->data->countIndividualsWithSources());
2147
    }
2148
2149
    public function totalIndisWithSourcesPercentage(): string
2150
    {
2151
        return $this->format->percentage(
2152
            $this->data->countIndividualsWithSources(),
2153
            $this->data->countIndividuals()
2154
        );
2155
    }
2156
2157
    public function totalIndividuals(): string
2158
    {
2159
        return I18N::number($this->data->countIndividuals());
2160
    }
2161
2162
    public function totalIndividualsPercentage(): string
2163
    {
2164
        return $this->format->percentage(
2165
            $this->data->countIndividuals(),
2166
            $this->data->countAllRecords()
2167
        );
2168
    }
2169
2170
    public function totalLiving(): string
2171
    {
2172
        return I18N::number($this->data->countIndividualsLiving());
2173
    }
2174
2175
    public function totalLivingPercentage(): string
2176
    {
2177
        return $this->format->percentage(
2178
            $this->data->countIndividualsLiving(),
2179
            $this->data->countIndividuals()
2180
        );
2181
    }
2182
2183
    public function totalMarriages(): string
2184
    {
2185
        return I18N::number($this->data->countFamiliesWithEvents(['MARR']));
2186
    }
2187
2188
    public function totalMarriedFemales(): string
2189
    {
2190
        return I18N::number($this->data->countMarriedFemales());
2191
    }
2192
2193
    public function totalMarriedMales(): string
2194
    {
2195
        return I18N::number($this->data->countMarriedMales());
2196
    }
2197
2198
    public function totalMedia(): string
2199
    {
2200
        return I18N::number($this->data->countMedia());
2201
    }
2202
2203
    public function totalMediaAudio(): string
2204
    {
2205
        return I18N::number($this->data->countMedia('audio'));
2206
    }
2207
2208
    public function totalMediaBook(): string
2209
    {
2210
        return I18N::number($this->data->countMedia('book'));
2211
    }
2212
2213
    public function totalMediaCard(): string
2214
    {
2215
        return I18N::number($this->data->countMedia('card'));
2216
    }
2217
2218
    public function totalMediaCertificate(): string
2219
    {
2220
        return I18N::number($this->data->countMedia('certificate'));
2221
    }
2222
2223
    public function totalMediaCoatOfArms(): string
2224
    {
2225
        return I18N::number($this->data->countMedia('coat'));
2226
    }
2227
2228
    public function totalMediaDocument(): string
2229
    {
2230
        return I18N::number($this->data->countMedia('document'));
2231
    }
2232
2233
    public function totalMediaElectronic(): string
2234
    {
2235
        return I18N::number($this->data->countMedia('electronic'));
2236
    }
2237
2238
    public function totalMediaFiche(): string
2239
    {
2240
        return I18N::number($this->data->countMedia('fiche'));
2241
    }
2242
2243
    public function totalMediaFilm(): string
2244
    {
2245
        return I18N::number($this->data->countMedia('film'));
2246
    }
2247
2248
    public function totalMediaMagazine(): string
2249
    {
2250
        return I18N::number($this->data->countMedia('magazine'));
2251
    }
2252
2253
    public function totalMediaManuscript(): string
2254
    {
2255
        return I18N::number($this->data->countMedia('manuscript'));
2256
    }
2257
2258
    public function totalMediaMap(): string
2259
    {
2260
        return I18N::number($this->data->countMedia('map'));
2261
    }
2262
2263
    public function totalMediaNewspaper(): string
2264
    {
2265
        return I18N::number($this->data->countMedia('newspaper'));
2266
    }
2267
2268
    public function totalMediaOther(): string
2269
    {
2270
        return I18N::number($this->data->countMedia('other'));
2271
    }
2272
2273
    public function totalMediaPainting(): string
2274
    {
2275
        return I18N::number($this->data->countMedia('painting'));
2276
    }
2277
2278
    public function totalMediaPhoto(): string
2279
    {
2280
        return I18N::number($this->data->countMedia('photo'));
2281
    }
2282
2283
    public function totalMediaTombstone(): string
2284
    {
2285
        return I18N::number($this->data->countMedia('tombstone'));
2286
    }
2287
2288
    public function totalMediaUnknown(): string
2289
    {
2290
        return I18N::number($this->data->countMedia(''));
2291
    }
2292
2293
    public function totalMediaVideo(): string
2294
    {
2295
        return I18N::number($this->data->countMedia('video'));
2296
    }
2297
2298
    public function totalNonAdmins(): string
2299
    {
2300
        return I18N::number($this->user_service->all()->count() - $this->user_service->administrators()->count());
2301
    }
2302
2303
    public function totalNotes(): string
2304
    {
2305
        return I18N::number($this->data->countNotes());
2306
    }
2307
2308
    public function totalNotesPercentage(): string
2309
    {
2310
        return $this->format->percentage(
2311
            $this->data->countNotes(),
2312
            $this->data->countAllRecords()
2313
        );
2314
    }
2315
2316
    public function totalPlaces(): string
2317
    {
2318
        return I18N::number($this->data->countAllPlaces());
2319
    }
2320
2321
    public function totalRecords(): string
2322
    {
2323
        return I18N::number($this->data->countAllRecords());
2324
    }
2325
2326
    public function totalRepositories(): string
2327
    {
2328
        return I18N::number($this->data->countRepositories());
2329
    }
2330
2331
    public function totalRepositoriesPercentage(): string
2332
    {
2333
        return $this->format->percentage(
2334
            $this->data->countRepositories(),
2335
            $this->data->countAllRecords()
2336
        );
2337
    }
2338
2339
    public function totalSexFemales(): string
2340
    {
2341
        return I18N::number($this->data->countIndividualsBySex('F'));
2342
    }
2343
2344
    public function totalSexFemalesPercentage(): string
2345
    {
2346
        return $this->format->percentage(
2347
            $this->data->countIndividualsBySex('F'),
2348
            $this->data->countIndividuals()
2349
        );
2350
    }
2351
2352
    public function totalSexMales(): string
2353
    {
2354
        return I18N::number($this->data->countIndividualsBySex('M'));
2355
    }
2356
2357
    public function totalSexMalesPercentage(): string
2358
    {
2359
        return $this->format->percentage(
2360
            $this->data->countIndividualsBySex('M'),
2361
            $this->data->countIndividuals()
2362
        );
2363
    }
2364
2365
    public function totalSexOther(): string
2366
    {
2367
        return I18N::number($this->data->countIndividualsBySex('X'));
2368
    }
2369
2370
    public function totalSexOtherPercentage(): string
2371
    {
2372
        return $this->format->percentage(
2373
            $this->data->countIndividualsBySex('X'),
2374
            $this->data->countIndividuals()
2375
        );
2376
    }
2377
2378
    public function totalSexUnknown(): string
2379
    {
2380
        return I18N::number($this->data->countIndividualsBySex('U'));
2381
    }
2382
2383
    public function totalSexUnknownPercentage(): string
2384
    {
2385
        return $this->format->percentage(
2386
            $this->data->countIndividualsBySex('U'),
2387
            $this->data->countIndividuals()
2388
        );
2389
    }
2390
2391
    public function totalSources(): string
2392
    {
2393
        return I18N::number($this->data->countSources());
2394
    }
2395
2396
    public function totalSourcesPercentage(): string
2397
    {
2398
        return $this->format->percentage(
2399
            $this->data->countSources(),
2400
            $this->data->countAllRecords()
2401
        );
2402
    }
2403
2404
    public function totalSurnames(string ...$names): string
2405
    {
2406
        return I18N::number($this->data->countSurnames($names));
2407
    }
2408
2409
    public function totalTreeNews(): string
2410
    {
2411
        return I18N::number($this->data->countTreeNews());
2412
    }
2413
2414
    public function totalUserFavorites(): string
2415
    {
2416
        return I18N::number($this->data->countUserfavorites());
2417
    }
2418
2419
    public function totalUserJournal(): string
2420
    {
2421
        return I18N::number($this->data->countUserJournal());
2422
    }
2423
2424
    public function totalUserMessages(): string
2425
    {
2426
        return I18N::number($this->data->countUserMessages());
2427
    }
2428
2429
    public function totalUsers(): string
2430
    {
2431
        return I18N::number($this->user_service->all()->count());
2432
    }
2433
2434
    public function userFavorites(): string
2435
    {
2436
        return $this->callBlock('user_favorites');
2437
    }
2438
2439
    public function userFullName(): string
2440
    {
2441
        return Auth::check() ? '<bdi>' . e(Auth::user()->realName()) . '</bdi>' : '';
2442
    }
2443
2444
    public function userId(): string
2445
    {
2446
        return (string) Auth::id();
2447
    }
2448
2449
    public function userName(string $visitor_text = ''): string
2450
    {
2451
        if (Auth::check()) {
2452
            return e(Auth::user()->userName());
2453
        }
2454
2455
        if ($visitor_text === '') {
2456
            return I18N::translate('Visitor');
2457
        }
2458
2459
        return e($visitor_text);
2460
    }
2461
2462
    public function usersLoggedIn(): string
2463
    {
2464
        return $this->data->usersLoggedIn();
2465
    }
2466
2467
    public function usersLoggedInList(): string
2468
    {
2469
        return $this->data->usersLoggedInList();
2470
    }
2471
2472
    public function webtreesVersion(): string
2473
    {
2474
        return Webtrees::VERSION;
2475
    }
2476
2477
    public function youngestFather(): string
2478
    {
2479
        return $this->data->parentsQuery('full', 'ASC', 'M', false);
2480
    }
2481
2482
    public function youngestFatherAge(string $show_years = '0'): string
2483
    {
2484
        return $this->data->parentsQuery('age', 'ASC', 'M', (bool) $show_years);
2485
    }
2486
2487
    public function youngestFatherName(): string
2488
    {
2489
        return $this->data->parentsQuery('name', 'ASC', 'M', false);
2490
    }
2491
2492
    public function youngestMarriageFemale(): string
2493
    {
2494
        return $this->data->marriageQuery('full', 'ASC', 'F', false);
2495
    }
2496
2497
    public function youngestMarriageFemaleAge(string $show_years = '0'): string
2498
    {
2499
        return $this->data->marriageQuery('age', 'ASC', 'F', (bool) $show_years);
2500
    }
2501
2502
    public function youngestMarriageFemaleName(): string
2503
    {
2504
        return $this->data->marriageQuery('name', 'ASC', 'F', false);
2505
    }
2506
2507
    public function youngestMarriageMale(): string
2508
    {
2509
        return $this->data->marriageQuery('full', 'ASC', 'M', false);
2510
    }
2511
2512
    public function youngestMarriageMaleAge(string $show_years = '0'): string
2513
    {
2514
        return $this->data->marriageQuery('age', 'ASC', 'M', (bool) $show_years);
2515
    }
2516
2517
    public function youngestMarriageMaleName(): string
2518
    {
2519
        return $this->data->marriageQuery('name', 'ASC', 'M', false);
2520
    }
2521
2522
    public function youngestMother(): string
2523
    {
2524
        return $this->data->parentsQuery('full', 'ASC', 'F', false);
2525
    }
2526
2527
    public function youngestMotherAge(string $show_years = '0'): string
2528
    {
2529
        return $this->data->parentsQuery('age', 'ASC', 'F', (bool) $show_years);
2530
    }
2531
2532
    public function youngestMotherName(): string
2533
    {
2534
        return $this->data->parentsQuery('name', 'ASC', 'F', false);
2535
    }
2536
}
2537