Statistics::gedcomUpdated()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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